From be493d996eb59b76b1edaf4d511eb5c3cfb1b66c Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Thu, 21 Nov 2024 19:26:41 +0700 Subject: [PATCH 01/20] feat: support permit swap --- api/_abis.ts | 49 ++++++++ api/_dexes/cross-swap.ts | 57 ++++++++++ api/_permit.ts | 144 ++++++++++++++++++++++++ api/swap/permit.ts | 41 +++++++ scripts/tests/swap-permit.ts | 212 +++++++++++++++++++++++++++++++++++ 5 files changed, 503 insertions(+) create mode 100644 api/_permit.ts create mode 100644 api/swap/permit.ts create mode 100644 scripts/tests/swap-permit.ts diff --git a/api/_abis.ts b/api/_abis.ts index da0bf6884..3dd4fc41f 100644 --- a/api/_abis.ts +++ b/api/_abis.ts @@ -77,3 +77,52 @@ export const MINIMAL_MULTICALL3_ABI = [ type: "function", }, ]; + +export const ERC20_PERMIT_ABI = [ + { + inputs: [], + stateMutability: "view", + type: "function", + name: "name", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + name: "nonces", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + }, + { + inputs: [], + name: "version", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "DOMAIN_SEPARATOR", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, +]; diff --git a/api/_dexes/cross-swap.ts b/api/_dexes/cross-swap.ts index 4d6d0e655..0ab6a20b0 100644 --- a/api/_dexes/cross-swap.ts +++ b/api/_dexes/cross-swap.ts @@ -26,6 +26,7 @@ import { getSpokePoolPeriphery } from "../_spoke-pool-periphery"; import { tagIntegratorId } from "../_integrator-id"; import { getMultiCallHandlerAddress } from "../_multicall-handler"; import { CHAIN_IDs } from "../_constants"; +import { getPermitTypedData } from "../_permit"; export type CrossSwapType = (typeof CROSS_SWAP_TYPE)[keyof typeof CROSS_SWAP_TYPE]; @@ -290,6 +291,62 @@ export async function buildCrossSwapTxForAllowanceHolder( }; } +export async function getCrossSwapTxForPermit( + crossSwapQuotes: CrossSwapQuotes, + permitDeadline: number +) { + const originChainId = crossSwapQuotes.crossSwap.inputToken.chainId; + const spokePoolPeriphery = getSpokePoolPeriphery("uniswap", originChainId); + const deposit = await extractDepositDataStruct(crossSwapQuotes); + + let methodName: string; + let argsWithoutSignature: Record; + if (crossSwapQuotes.originSwapQuote) { + methodName = "swapAndBridgeWithPermit"; + argsWithoutSignature = { + swapToken: crossSwapQuotes.originSwapQuote.tokenIn.address, + acrossInputToken: crossSwapQuotes.originSwapQuote.tokenOut.address, + routerCalldata: crossSwapQuotes.originSwapQuote.swapTx.data, + swapTokenAmount: crossSwapQuotes.originSwapQuote.maximumAmountIn, + minExpectedInputTokenAmount: crossSwapQuotes.originSwapQuote.minAmountOut, + depositData: deposit, + deadline: permitDeadline, + }; + } else { + methodName = "depositWithPermit"; + argsWithoutSignature = { + acrossInputToken: crossSwapQuotes.bridgeQuote.inputToken.address, + acrossInputAmount: crossSwapQuotes.bridgeQuote.inputAmount, + depositData: deposit, + deadline: permitDeadline, + }; + } + + const permitTypedData = await getPermitTypedData({ + tokenAddress: + crossSwapQuotes.originSwapQuote?.tokenIn.address || + crossSwapQuotes.bridgeQuote.inputToken.address, + chainId: originChainId, + ownerAddress: crossSwapQuotes.crossSwap.depositor, + spenderAddress: spokePoolPeriphery.address, + value: + crossSwapQuotes.originSwapQuote?.maximumAmountIn || + crossSwapQuotes.bridgeQuote.inputAmount, + deadline: permitDeadline, + }); + return { + permit: { + eip712: permitTypedData.eip712, + }, + swapTx: { + chainId: originChainId, + to: spokePoolPeriphery.address, + methodName, + argsWithoutSignature, + }, + }; +} + async function extractDepositDataStruct(crossSwapQuotes: CrossSwapQuotes) { const originChainId = crossSwapQuotes.crossSwap.inputToken.chainId; const destinationChainId = crossSwapQuotes.crossSwap.outputToken.chainId; diff --git a/api/_permit.ts b/api/_permit.ts new file mode 100644 index 000000000..90e7211ad --- /dev/null +++ b/api/_permit.ts @@ -0,0 +1,144 @@ +import { BigNumberish, ethers } from "ethers"; + +import { getProvider } from "./_utils"; +import { ERC20_PERMIT_ABI } from "./_abis"; + +export async function getPermitTypedData(params: { + tokenAddress: string; + chainId: number; + ownerAddress: string; + spenderAddress: string; + value: BigNumberish; + deadline: number; + eip712DomainVersion?: number; +}) { + const provider = getProvider(params.chainId); + const erc20Permit = new ethers.Contract( + params.tokenAddress, + ERC20_PERMIT_ABI, + provider + ); + + const [ + nameResult, + versionFromContractResult, + nonceResult, + domainSeparatorResult, + ] = await Promise.allSettled([ + erc20Permit.name(), + erc20Permit.version(), + erc20Permit.nonces(params.ownerAddress), + erc20Permit.DOMAIN_SEPARATOR(), + ]); + + if ( + nameResult.status === "rejected" || + nonceResult.status === "rejected" || + domainSeparatorResult.status === "rejected" + ) { + const error = + nameResult.status === "rejected" + ? nameResult.reason + : nonceResult.status === "rejected" + ? nonceResult.reason + : domainSeparatorResult.status === "rejected" + ? domainSeparatorResult.reason + : new Error("Unknown error"); + throw new Error(`Contract ${params.tokenAddress} does not support permit`, { + cause: error, + }); + } + + const name = nameResult.value; + const versionFromContract = + versionFromContractResult.status === "fulfilled" + ? versionFromContractResult.value + : undefined; + const nonce = nonceResult.value; + const domainSeparator = domainSeparatorResult.value; + + const eip712DomainVersion = [1, 2, "1", "2"].includes(versionFromContract) + ? Number(versionFromContract) + : params.eip712DomainVersion || 1; + + const domainSeparatorHash = ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "bytes32", "uint256", "address"], + [ + ethers.utils.id( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ), + ethers.utils.id(name), + ethers.utils.id(eip712DomainVersion.toString()), + params.chainId, + params.tokenAddress, + ] + ) + ); + + if (domainSeparator !== domainSeparatorHash) { + throw new Error("EIP712 domain separator mismatch"); + } + + return { + domainSeparator, + eip712: { + types: { + EIP712Domain: [ + { + name: "name", + type: "string", + }, + { + name: "version", + type: "string", + }, + { + name: "chainId", + type: "uint256", + }, + { + name: "verifyingContract", + type: "address", + }, + ], + Permit: [ + { + name: "owner", + type: "address", + }, + { + name: "spender", + type: "address", + }, + { + name: "value", + type: "uint256", + }, + { + name: "nonce", + type: "uint256", + }, + { + name: "deadline", + type: "uint256", + }, + ], + }, + primaryType: "Permit", + domain: { + name, + version: eip712DomainVersion.toString(), + chainId: params.chainId, + verifyingContract: params.tokenAddress, + }, + message: { + owner: params.ownerAddress, + spender: params.spenderAddress, + value: String(params.value), + nonce: String(nonce), + deadline: String(params.deadline), + }, + }, + }; +} diff --git a/api/swap/permit.ts b/api/swap/permit.ts new file mode 100644 index 000000000..6d65b8b6c --- /dev/null +++ b/api/swap/permit.ts @@ -0,0 +1,41 @@ +import { VercelResponse } from "@vercel/node"; + +import { TypedVercelRequest } from "../_types"; +import { getLogger, handleErrorCondition } from "../_utils"; +import { getCrossSwapTxForPermit } from "../_dexes/cross-swap"; +import { handleBaseSwapQueryParams, BaseSwapQueryParams } from "./_utils"; + +const handler = async ( + request: TypedVercelRequest, + response: VercelResponse +) => { + const logger = getLogger(); + logger.debug({ + at: "Swap/permit", + message: "Query data", + query: request.query, + }); + try { + const { crossSwapQuotes } = await handleBaseSwapQueryParams(request); + + const permitDeadline = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365; // 1 year + + const crossSwapTxForPermit = await getCrossSwapTxForPermit( + crossSwapQuotes, + permitDeadline + ); + + const responseJson = crossSwapTxForPermit; + + logger.debug({ + at: "Swap/allowance", + message: "Response data", + responseJson, + }); + response.status(200).json(responseJson); + } catch (error: unknown) { + return handleErrorCondition("swap/allowance", response, logger, error); + } +}; + +export default handler; diff --git a/scripts/tests/swap-permit.ts b/scripts/tests/swap-permit.ts new file mode 100644 index 000000000..c22053f54 --- /dev/null +++ b/scripts/tests/swap-permit.ts @@ -0,0 +1,212 @@ +import axios from "axios"; +import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; +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. + */ + +const depositor = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; +const MIN_OUTPUT_CASES = [ + // B2B + { + 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, + }, + }, + { + labels: ["B2B", "MIN_OUTPUT", "Arbitrum ETH - Base USDC"], + params: { + minOutputAmount: ethers.utils.parseUnits("3", 6).toString(), + inputToken: ethers.constants.AddressZero, + originChainId: CHAIN_IDs.ARBITRUM, + outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE], + destinationChainId: CHAIN_IDs.BASE, + depositor, + }, + }, + // B2A + { + 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 + { + 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 + { + 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() { + 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/permit`, { + 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.params.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); + } + } + } +} + +swap() + .then(() => console.log("Done")) + .catch(console.error); From 2c68621f46ea6cd21e7edd6b9a7b4d6906a18521 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Mon, 25 Nov 2024 21:22:00 +0700 Subject: [PATCH 02/20] fixup --- api/swap/permit.ts | 4 +- scripts/tests/_swap-utils.ts | 145 +++++++++++++++++++++++ scripts/tests/swap-allowance.ts | 167 ++------------------------- scripts/tests/swap-permit.ts | 199 ++------------------------------ 4 files changed, 164 insertions(+), 351 deletions(-) create mode 100644 scripts/tests/_swap-utils.ts diff --git a/api/swap/permit.ts b/api/swap/permit.ts index 6d65b8b6c..321728d03 100644 --- a/api/swap/permit.ts +++ b/api/swap/permit.ts @@ -28,13 +28,13 @@ const handler = async ( const responseJson = crossSwapTxForPermit; logger.debug({ - at: "Swap/allowance", + at: "Swap/permit", message: "Response data", responseJson, }); response.status(200).json(responseJson); } catch (error: unknown) { - return handleErrorCondition("swap/allowance", response, logger, error); + return handleErrorCondition("swap/permit", response, logger, error); } }; diff --git a/scripts/tests/_swap-utils.ts b/scripts/tests/_swap-utils.ts new file mode 100644 index 000000000..e7fcba279 --- /dev/null +++ b/scripts/tests/_swap-utils.ts @@ -0,0 +1,145 @@ +import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; +import { ethers } from "ethers"; +import dotenv from "dotenv"; +dotenv.config(); + +export const { SWAP_API_BASE_URL = "http://localhost:3000" } = process.env; + +export const depositor = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; +export const originChainId = CHAIN_IDs.OPTIMISM; +export const destinationChainId = CHAIN_IDs.ARBITRUM; +export const anyDestinationOutputTokens = { + [CHAIN_IDs.ARBITRUM]: "0x74885b4D524d497261259B38900f54e6dbAd2210", // APE +}; +export const MIN_OUTPUT_CASES = [ + // B2B + { + labels: ["B2B", "MIN_OUTPUT", "USDC - USDC"], + params: { + amount: ethers.utils.parseUnits("1", 6).toString(), + tradeType: "minOutput", + inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[originChainId], + originChainId, + outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[destinationChainId], + destinationChainId, + depositor, + }, + }, + { + labels: ["B2B", "MIN_OUTPUT", "Native ETH - Native ETH"], + params: { + amount: ethers.utils.parseUnits("0.001", 18).toString(), + tradeType: "minOutput", + inputToken: ethers.constants.AddressZero, + originChainId, + outputToken: ethers.constants.AddressZero, + destinationChainId, + depositor, + }, + }, + { + labels: ["B2B", "MIN_OUTPUT", "USDC - Native ETH"], + params: { + amount: ethers.utils.parseUnits("0.001", 18).toString(), + tradeType: "minOutput", + inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[originChainId], + originChainId, + outputToken: ethers.constants.AddressZero, + destinationChainId, + depositor, + }, + }, + { + labels: ["B2B", "MIN_OUTPUT", "Native ETH - USDC"], + params: { + amount: ethers.utils.parseUnits("3", 6).toString(), + tradeType: "minOutput", + inputToken: ethers.constants.AddressZero, + originChainId, + outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[destinationChainId], + destinationChainId, + depositor, + }, + }, + // B2A + { + labels: ["B2A", "MIN_OUTPUT", "USDC - APE"], + params: { + amount: ethers.utils.parseUnits("1", 18).toString(), + tradeType: "minOutput", + inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[originChainId], + originChainId, + outputToken: anyDestinationOutputTokens[destinationChainId], + destinationChainId, + depositor, + }, + }, + { + labels: ["B2A", "MIN_OUTPUT", "USDC - Native ETH"], + params: { + amount: ethers.utils.parseUnits("0.001", 18).toString(), + tradeType: "minOutput", + inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[originChainId], + originChainId, + outputToken: ethers.constants.AddressZero, + destinationChainId, + depositor, + }, + }, + // A2B + { + labels: ["A2B", "MIN_OUTPUT", "bridged USDC - USDC"], + params: { + amount: ethers.utils.parseUnits("1", 6).toString(), + tradeType: "minOutput", + inputToken: + TOKEN_SYMBOLS_MAP["USDC.e"].addresses[originChainId] || + TOKEN_SYMBOLS_MAP.USDbC.addresses[originChainId], + originChainId, + outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[destinationChainId], + destinationChainId, + depositor, + }, + }, + // A2A + { + labels: ["A2A", "MIN_OUTPUT", "bridged USDC - APE"], + params: { + amount: ethers.utils.parseUnits("1", 18).toString(), + tradeType: "minOutput", + inputToken: + TOKEN_SYMBOLS_MAP["USDC.e"].addresses[originChainId] || + TOKEN_SYMBOLS_MAP.USDbC.addresses[originChainId], + originChainId: CHAIN_IDs.BASE, + outputToken: anyDestinationOutputTokens[destinationChainId], // APE Coin + destinationChainId, + depositor, + }, + }, +]; +export const EXACT_OUTPUT_CASES = MIN_OUTPUT_CASES.map((testCase) => ({ + labels: testCase.labels.map((label) => label.replace("MIN", "EXACT")), + params: { + ...testCase.params, + tradeType: "exactOutput", + }, +})); + +export function filterTestCases( + testCases: { + labels: string[]; + params: { [key: string]: any }; + }[], + filterString: string +) { + 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; + }); + return filteredTestCases; +} diff --git a/scripts/tests/swap-allowance.ts b/scripts/tests/swap-allowance.ts index a32311ccd..382e21d13 100644 --- a/scripts/tests/swap-allowance.ts +++ b/scripts/tests/swap-allowance.ts @@ -1,176 +1,27 @@ import axios from "axios"; -import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; -import { ethers, Wallet } from "ethers"; +import { Wallet } from "ethers"; import dotenv from "dotenv"; import { getProvider } from "../../api/_utils"; +import { + filterTestCases, + MIN_OUTPUT_CASES, + EXACT_OUTPUT_CASES, + SWAP_API_BASE_URL, +} from "./_swap-utils"; dotenv.config(); /** * Manual test script for the swap API. Should be converted to a proper test suite. */ - -const depositor = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; -const originChainId = CHAIN_IDs.OPTIMISM; -const destinationChainId = CHAIN_IDs.ARBITRUM; -const anyDestinationOutputTokens = { - [CHAIN_IDs.ARBITRUM]: "0x74885b4D524d497261259B38900f54e6dbAd2210", // APE -}; - -const MIN_OUTPUT_CASES = [ - // B2B - { - labels: ["B2B", "MIN_OUTPUT", "USDC - USDC"], - params: { - amount: ethers.utils.parseUnits("1", 6).toString(), - tradeType: "minOutput", - inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[originChainId], - originChainId, - outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[destinationChainId], - destinationChainId, - depositor, - }, - }, - { - labels: ["B2B", "MIN_OUTPUT", "Native ETH - Native ETH"], - params: { - amount: ethers.utils.parseUnits("0.001", 18).toString(), - tradeType: "minOutput", - inputToken: ethers.constants.AddressZero, - originChainId, - outputToken: ethers.constants.AddressZero, - destinationChainId, - depositor, - }, - }, - { - labels: ["B2B", "MIN_OUTPUT", "USDC - Native ETH"], - params: { - amount: ethers.utils.parseUnits("0.001", 18).toString(), - tradeType: "minOutput", - inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[originChainId], - originChainId, - outputToken: ethers.constants.AddressZero, - destinationChainId, - depositor, - }, - }, - { - labels: ["B2B", "MIN_OUTPUT", "Native ETH - USDC"], - params: { - amount: ethers.utils.parseUnits("3", 6).toString(), - tradeType: "minOutput", - inputToken: ethers.constants.AddressZero, - originChainId, - outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[destinationChainId], - destinationChainId, - depositor, - }, - }, - // B2A - { - labels: ["B2A", "MIN_OUTPUT", "USDC - APE"], - params: { - amount: ethers.utils.parseUnits("3", 18).toString(), - tradeType: "minOutput", - inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[originChainId], - originChainId, - outputToken: anyDestinationOutputTokens[destinationChainId], - destinationChainId, - depositor, - }, - }, - { - labels: ["B2A", "MIN_OUTPUT", "USDC - Native ETH"], - params: { - amount: ethers.utils.parseUnits("0.001", 18).toString(), - tradeType: "minOutput", - inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[originChainId], - originChainId, - outputToken: ethers.constants.AddressZero, - destinationChainId, - depositor, - }, - }, - // A2B - { - labels: ["A2B", "MIN_OUTPUT", "bridged USDC - USDC"], - params: { - amount: ethers.utils.parseUnits("1", 6).toString(), - tradeType: "minOutput", - inputToken: - TOKEN_SYMBOLS_MAP["USDC.e"].addresses[originChainId] || - TOKEN_SYMBOLS_MAP.USDbC.addresses[originChainId], - originChainId, - outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[destinationChainId], - destinationChainId, - depositor, - }, - }, - { - labels: ["A2B", "MIN_OUTPUT", "USDC - WETH"], - params: { - amount: ethers.utils.parseUnits("0.001", 18).toString(), - tradeType: "minOutput", - inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[originChainId], - originChainId, - outputToken: TOKEN_SYMBOLS_MAP.WETH.addresses[destinationChainId], - destinationChainId, - depositor, - }, - }, - { - labels: ["A2B", "MIN_OUTPUT", "WETH - USDC"], - params: { - amount: ethers.utils.parseUnits("1", 6).toString(), - tradeType: "minOutput", - inputToken: TOKEN_SYMBOLS_MAP.WETH.addresses[originChainId], - originChainId, - outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[destinationChainId], - destinationChainId, - depositor, - }, - }, - // A2A - { - labels: ["A2A", "MIN_OUTPUT", "bridged USDC - APE"], - params: { - amount: ethers.utils.parseUnits("1", 18).toString(), - tradeType: "minOutput", - inputToken: - TOKEN_SYMBOLS_MAP["USDC.e"].addresses[originChainId] || - TOKEN_SYMBOLS_MAP.USDbC.addresses[originChainId], - originChainId, - outputToken: anyDestinationOutputTokens[destinationChainId], // APE Coin - destinationChainId, - depositor, - }, - }, -]; -const EXACT_OUTPUT_CASES = MIN_OUTPUT_CASES.map((testCase) => ({ - labels: testCase.labels.map((label) => label.replace("MIN", "EXACT")), - params: { - ...testCase.params, - tradeType: "exactOutput", - }, -})); - async function swap() { 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; - }); + const filteredTestCases = filterTestCases(testCases, filterString); for (const testCase of filteredTestCases) { console.log("\nTest case:", testCase.labels.join(" ")); console.log("Params:", testCase.params); const response = await axios.get( - `http://localhost:3000/api/swap/allowance`, + `${SWAP_API_BASE_URL}/api/swap/allowance`, { params: testCase.params, } diff --git a/scripts/tests/swap-permit.ts b/scripts/tests/swap-permit.ts index c22053f54..9416e9595 100644 --- a/scripts/tests/swap-permit.ts +++ b/scripts/tests/swap-permit.ts @@ -1,209 +1,26 @@ import axios from "axios"; -import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; -import { ethers, Wallet } from "ethers"; import dotenv from "dotenv"; -import { getProvider } from "../../api/_utils"; +import { + filterTestCases, + MIN_OUTPUT_CASES, + EXACT_OUTPUT_CASES, + SWAP_API_BASE_URL, +} from "./_swap-utils"; dotenv.config(); /** * Manual test script for the swap API. Should be converted to a proper test suite. */ - -const depositor = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; -const MIN_OUTPUT_CASES = [ - // B2B - { - 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, - }, - }, - { - labels: ["B2B", "MIN_OUTPUT", "Arbitrum ETH - Base USDC"], - params: { - minOutputAmount: ethers.utils.parseUnits("3", 6).toString(), - inputToken: ethers.constants.AddressZero, - originChainId: CHAIN_IDs.ARBITRUM, - outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE], - destinationChainId: CHAIN_IDs.BASE, - depositor, - }, - }, - // B2A - { - 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 - { - 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 - { - 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() { 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; - }); + const filteredTestCases = filterTestCases(testCases, filterString); for (const testCase of filteredTestCases) { console.log("\nTest case:", testCase.labels.join(" ")); - const response = await axios.get(`http://localhost:3000/api/swap/permit`, { + const response = await axios.get(`${SWAP_API_BASE_URL}/api/swap/permit`, { 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.params.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 432c160dc7e1d2268a2b12e485b3d80a37545d33 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Tue, 3 Dec 2024 17:16:58 +0100 Subject: [PATCH 03/20] refactor: swap test scripts --- scripts/tests/_swap-utils.ts | 86 +++++++++++++++++++++++++++++++-- scripts/tests/swap-allowance.ts | 72 ++------------------------- scripts/tests/swap-permit.ts | 36 +++++--------- 3 files changed, 99 insertions(+), 95 deletions(-) diff --git a/scripts/tests/_swap-utils.ts b/scripts/tests/_swap-utils.ts index e7fcba279..17fa146a8 100644 --- a/scripts/tests/_swap-utils.ts +++ b/scripts/tests/_swap-utils.ts @@ -1,6 +1,8 @@ import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; -import { ethers } from "ethers"; +import { ethers, Wallet } from "ethers"; import dotenv from "dotenv"; +import axios from "axios"; +import { getProvider } from "../../api/_utils"; dotenv.config(); export const { SWAP_API_BASE_URL = "http://localhost:3000" } = process.env; @@ -65,7 +67,7 @@ export const MIN_OUTPUT_CASES = [ { labels: ["B2A", "MIN_OUTPUT", "USDC - APE"], params: { - amount: ethers.utils.parseUnits("1", 18).toString(), + amount: ethers.utils.parseUnits("3", 18).toString(), tradeType: "minOutput", inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[originChainId], originChainId, @@ -101,6 +103,30 @@ export const MIN_OUTPUT_CASES = [ depositor, }, }, + { + labels: ["A2B", "MIN_OUTPUT", "USDC - WETH"], + params: { + amount: ethers.utils.parseUnits("0.001", 18).toString(), + tradeType: "minOutput", + inputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[originChainId], + originChainId, + outputToken: TOKEN_SYMBOLS_MAP.WETH.addresses[destinationChainId], + destinationChainId, + depositor, + }, + }, + { + labels: ["A2B", "MIN_OUTPUT", "WETH - USDC"], + params: { + amount: ethers.utils.parseUnits("1", 6).toString(), + tradeType: "minOutput", + inputToken: TOKEN_SYMBOLS_MAP.WETH.addresses[originChainId], + originChainId, + outputToken: TOKEN_SYMBOLS_MAP.USDC.addresses[destinationChainId], + destinationChainId, + depositor, + }, + }, // A2A { labels: ["A2A", "MIN_OUTPUT", "bridged USDC - APE"], @@ -110,7 +136,7 @@ export const MIN_OUTPUT_CASES = [ inputToken: TOKEN_SYMBOLS_MAP["USDC.e"].addresses[originChainId] || TOKEN_SYMBOLS_MAP.USDbC.addresses[originChainId], - originChainId: CHAIN_IDs.BASE, + originChainId, outputToken: anyDestinationOutputTokens[destinationChainId], // APE Coin destinationChainId, depositor, @@ -143,3 +169,57 @@ export function filterTestCases( }); return filteredTestCases; } + +export async function swap() { + const filterString = process.argv[2]; + const testCases = [...MIN_OUTPUT_CASES, ...EXACT_OUTPUT_CASES]; + const filteredTestCases = filterTestCases(testCases, filterString); + for (const testCase of filteredTestCases) { + console.log("\nTest case:", testCase.labels.join(" ")); + console.log("Params:", testCase.params); + const response = await axios.get( + `${SWAP_API_BASE_URL}/api/swap/allowance`, + { + 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.params.originChainId) + ); + + if (response.data.approvalTxns) { + console.log("Approval needed..."); + let step = 1; + for (const approvalTxn of response.data.approvalTxns) { + const stepLabel = `(${step}/${response.data.approvalTxns.length})`; + const tx = await wallet.sendTransaction({ + to: approvalTxn.to, + data: approvalTxn.data, + }); + console.log(`${stepLabel} Approval tx hash:`, tx.hash); + await tx.wait(); + console.log(`${stepLabel} Approval tx mined`); + step++; + } + } + + try { + const tx = await wallet.sendTransaction({ + to: response.data.swapTx.to, + data: response.data.swapTx.data, + value: response.data.swapTx.value, + gasLimit: response.data.swapTx.gas, + gasPrice: response.data.swapTx.gasPrice, + }); + console.log("Tx hash: ", tx.hash); + await tx.wait(); + console.log("Tx mined"); + } catch (e) { + console.error("Tx reverted", e); + } + } + } +} diff --git a/scripts/tests/swap-allowance.ts b/scripts/tests/swap-allowance.ts index 382e21d13..b4886ae26 100644 --- a/scripts/tests/swap-allowance.ts +++ b/scripts/tests/swap-allowance.ts @@ -1,73 +1,11 @@ -import axios from "axios"; -import { Wallet } from "ethers"; -import dotenv from "dotenv"; -import { getProvider } from "../../api/_utils"; -import { - filterTestCases, - MIN_OUTPUT_CASES, - EXACT_OUTPUT_CASES, - SWAP_API_BASE_URL, -} from "./_swap-utils"; -dotenv.config(); +import { swap } from "./_swap-utils"; -/** - * Manual test script for the swap API. Should be converted to a proper test suite. - */ -async function swap() { - const filterString = process.argv[2]; - const testCases = [...MIN_OUTPUT_CASES, ...EXACT_OUTPUT_CASES]; - const filteredTestCases = filterTestCases(testCases, filterString); - for (const testCase of filteredTestCases) { - console.log("\nTest case:", testCase.labels.join(" ")); - console.log("Params:", testCase.params); - const response = await axios.get( - `${SWAP_API_BASE_URL}/api/swap/allowance`, - { - 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.params.originChainId) - ); - - if (response.data.approvalTxns) { - console.log("Approval needed..."); - let step = 1; - for (const approvalTxn of response.data.approvalTxns) { - const stepLabel = `(${step}/${response.data.approvalTxns.length})`; - const tx = await wallet.sendTransaction({ - to: approvalTxn.to, - data: approvalTxn.data, - }); - console.log(`${stepLabel} Approval tx hash:`, tx.hash); - await tx.wait(); - console.log(`${stepLabel} Approval tx mined`); - step++; - } - } - - try { - const tx = await wallet.sendTransaction({ - to: response.data.swapTx.to, - data: response.data.swapTx.data, - value: response.data.swapTx.value, - gasLimit: response.data.swapTx.gas, - gasPrice: response.data.swapTx.gasPrice, - }); - console.log("Tx hash: ", tx.hash); - await tx.wait(); - console.log("Tx mined"); - } catch (e) { - console.error("Tx reverted", e); - } - } - } +async function swapWithAllowance() { + console.log("Swapping with allowance..."); + await swap(); } -swap() +swapWithAllowance() .then(() => console.log("Done")) .catch((e) => { console.error(e); diff --git a/scripts/tests/swap-permit.ts b/scripts/tests/swap-permit.ts index 9416e9595..e448c3f78 100644 --- a/scripts/tests/swap-permit.ts +++ b/scripts/tests/swap-permit.ts @@ -1,29 +1,15 @@ -import axios from "axios"; -import dotenv from "dotenv"; -import { - filterTestCases, - MIN_OUTPUT_CASES, - EXACT_OUTPUT_CASES, - SWAP_API_BASE_URL, -} from "./_swap-utils"; -dotenv.config(); +import { swap } from "./_swap-utils"; -/** - * Manual test script for the swap API. Should be converted to a proper test suite. - */ -async function swap() { - const filterString = process.argv[2]; - const testCases = [...MIN_OUTPUT_CASES, ...EXACT_OUTPUT_CASES]; - const filteredTestCases = filterTestCases(testCases, filterString); - for (const testCase of filteredTestCases) { - console.log("\nTest case:", testCase.labels.join(" ")); - const response = await axios.get(`${SWAP_API_BASE_URL}/api/swap/permit`, { - params: testCase.params, - }); - console.log(response.data); - } +async function swapWithPermit() { + console.log("Swapping with permit..."); + await swap(); } -swap() +swapWithPermit() .then(() => console.log("Done")) - .catch(console.error); + .catch((e) => { + console.error(e); + if (e.response?.data) { + console.log("Tx for debug sim:", e.response.data.transaction); + } + }); From 2cd2a7a1644f0fbdf2bbc7597bd92054300981bd Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Tue, 3 Dec 2024 17:48:58 +0100 Subject: [PATCH 04/20] wip --- api/_dexes/cross-swap.ts | 2 +- api/swap/_utils.ts | 6 +++--- api/swap/allowance.ts | 2 +- api/swap/permit.ts | 28 ++++++++++++++++++++++++---- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/api/_dexes/cross-swap.ts b/api/_dexes/cross-swap.ts index 0ab6a20b0..fed297979 100644 --- a/api/_dexes/cross-swap.ts +++ b/api/_dexes/cross-swap.ts @@ -296,12 +296,12 @@ export async function getCrossSwapTxForPermit( permitDeadline: number ) { const originChainId = crossSwapQuotes.crossSwap.inputToken.chainId; - const spokePoolPeriphery = getSpokePoolPeriphery("uniswap", originChainId); const deposit = await extractDepositDataStruct(crossSwapQuotes); let methodName: string; let argsWithoutSignature: Record; if (crossSwapQuotes.originSwapQuote) { + const spokePoolPeriphery = crossSwapQuotes.originSwapQuote.peripheryAddress; methodName = "swapAndBridgeWithPermit"; argsWithoutSignature = { swapToken: crossSwapQuotes.originSwapQuote.tokenIn.address, diff --git a/api/swap/_utils.ts b/api/swap/_utils.ts index bde17a64a..e1c535899 100644 --- a/api/swap/_utils.ts +++ b/api/swap/_utils.ts @@ -45,9 +45,9 @@ export const BaseSwapQueryParamsSchema = type({ export type BaseSwapQueryParams = Infer; -export async function handleBaseSwapQueryParams({ - query, -}: TypedVercelRequest) { +export async function handleBaseSwapQueryParams( + query: TypedVercelRequest["query"] +) { assert(query, BaseSwapQueryParamsSchema); const { diff --git a/api/swap/allowance.ts b/api/swap/allowance.ts index 43c0013cd..45b9e556b 100644 --- a/api/swap/allowance.ts +++ b/api/swap/allowance.ts @@ -38,7 +38,7 @@ const handler = async ( integratorId, skipOriginTxEstimation, isInputNative, - } = await handleBaseSwapQueryParams(request); + } = await handleBaseSwapQueryParams(request.query); const crossSwapTx = await buildCrossSwapTxForAllowanceHolder( crossSwapQuotes, diff --git a/api/swap/permit.ts b/api/swap/permit.ts index 321728d03..370bfc795 100644 --- a/api/swap/permit.ts +++ b/api/swap/permit.ts @@ -1,12 +1,22 @@ import { VercelResponse } from "@vercel/node"; +import { assert, Infer, optional, type } from "superstruct"; import { TypedVercelRequest } from "../_types"; -import { getLogger, handleErrorCondition } from "../_utils"; +import { getLogger, handleErrorCondition, positiveIntStr } from "../_utils"; import { getCrossSwapTxForPermit } from "../_dexes/cross-swap"; import { handleBaseSwapQueryParams, BaseSwapQueryParams } from "./_utils"; +export const PermitSwapQueryParamsSchema = type({ + permitDeadline: optional(positiveIntStr()), +}); + +export type PermitSwapQueryParams = Infer; + +const DEFAULT_PERMIT_DEADLINE = + Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365; // 1 year + const handler = async ( - request: TypedVercelRequest, + request: TypedVercelRequest, response: VercelResponse ) => { const logger = getLogger(); @@ -16,10 +26,20 @@ const handler = async ( query: request.query, }); try { - const { crossSwapQuotes } = await handleBaseSwapQueryParams(request); + // `/swap/permit` specific params validation + const { permitDeadline: _permitDeadline, ...restQuery } = request.query; + assert( + { + permitDeadline: _permitDeadline, + }, + PermitSwapQueryParamsSchema + ); + const permitDeadline = Number(_permitDeadline ?? DEFAULT_PERMIT_DEADLINE); - const permitDeadline = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365; // 1 year + // `/swap` specific params validation + quote generation + const { crossSwapQuotes } = await handleBaseSwapQueryParams(restQuery); + // Build tx for permit const crossSwapTxForPermit = await getCrossSwapTxForPermit( crossSwapQuotes, permitDeadline From 5a67d42a8c33a22439b3b47d468cbe51e8f440c0 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Fri, 20 Dec 2024 12:51:24 +0100 Subject: [PATCH 05/20] feat: refactor + support permit --- api/_dexes/1inch.ts | 2 +- ...uote-resolver.ts => cross-swap-service.ts} | 407 +++-- api/_dexes/cross-swap.ts | 392 ----- api/_dexes/types.ts | 55 +- api/_dexes/uniswap/swap-quoter.ts | 2 +- api/_dexes/uniswap/swap-router-02.ts | 70 +- api/_dexes/uniswap/universal-router.ts | 38 +- api/_dexes/uniswap/utils.ts | 86 +- api/_dexes/utils.ts | 343 ++-- api/_permit.ts | 9 +- api/_spoke-pool-periphery.ts | 262 ++- api/_swap-and-bridge.ts | 108 ++ api/_typechain/SpokePoolPeripheryProxy.ts | 268 +++ api/_typechain/SpokePoolV3Periphery.ts | 749 +++++---- .../SpokePoolPeripheryProxy__factory.ts | 251 +++ .../factories/SpokePoolV3Periphery.ts | 838 ---------- .../SpokePoolV3Periphery__factory.ts | 1433 +++++++++++++++++ api/_utils.ts | 9 +- api/swap-quote.ts | 2 +- api/swap/_utils.ts | 43 +- api/swap/allowance.ts | 192 +-- api/swap/approval/_utils.ts | 150 ++ api/swap/approval/index.ts | 224 +++ api/swap/permit/_utils.ts | 127 ++ api/swap/{permit.ts => permit/index.ts} | 57 +- scripts/generate-routes.ts | 65 +- scripts/tests/_swap-utils.ts | 11 +- scripts/tests/swap-allowance.ts | 2 +- scripts/tests/swap-permit.ts | 2 +- ...24e63716afAcE30C9a417E0542281869f7d9e.json | 5 +- ...6fA914353c44b2E33eBE05f21846F1048bEda.json | 35 +- 31 files changed, 3998 insertions(+), 2239 deletions(-) rename api/_dexes/{uniswap/quote-resolver.ts => cross-swap-service.ts} (64%) delete mode 100644 api/_dexes/cross-swap.ts create mode 100644 api/_swap-and-bridge.ts create mode 100644 api/_typechain/SpokePoolPeripheryProxy.ts create mode 100644 api/_typechain/factories/SpokePoolPeripheryProxy__factory.ts delete mode 100644 api/_typechain/factories/SpokePoolV3Periphery.ts create mode 100644 api/_typechain/factories/SpokePoolV3Periphery__factory.ts create mode 100644 api/swap/approval/_utils.ts create mode 100644 api/swap/approval/index.ts create mode 100644 api/swap/permit/_utils.ts rename api/swap/{permit.ts => permit/index.ts} (50%) diff --git a/api/_dexes/1inch.ts b/api/_dexes/1inch.ts index b3febda27..f87c2e79c 100644 --- a/api/_dexes/1inch.ts +++ b/api/_dexes/1inch.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { Swap, OriginSwapQuoteAndCalldata } from "./types"; -import { getSwapAndBridgeAddress } from "./utils"; +import { getSwapAndBridgeAddress } from "../_swap-and-bridge"; export async function get1inchQuoteForOriginSwapExactInput( swap: Omit diff --git a/api/_dexes/uniswap/quote-resolver.ts b/api/_dexes/cross-swap-service.ts similarity index 64% rename from api/_dexes/uniswap/quote-resolver.ts rename to api/_dexes/cross-swap-service.ts index e1bfae60a..d1f3dbfd9 100644 --- a/api/_dexes/uniswap/quote-resolver.ts +++ b/api/_dexes/cross-swap-service.ts @@ -1,54 +1,144 @@ -import { BigNumber } from "ethers"; import { TradeType } from "@uniswap/sdk-core"; import { - getRouteByInputTokenAndDestinationChain, - getTokenByAddress, getBridgeQuoteForMinOutput, - getRoutesByChainIds, + getRouteByInputTokenAndDestinationChain, getRouteByOutputTokenAndOriginChain, + getRoutesByChainIds, + getTokenByAddress, Profiler, -} from "../../_utils"; -import { - buildMulticallHandlerMessage, - encodeApproveCalldata, - encodeDrainCalldata, - encodeTransferCalldata, - encodeWethWithdrawCalldata, - getMultiCallHandlerAddress, -} from "../../_multicall-handler"; -import { - Token as AcrossToken, - CrossSwap, - CrossSwapQuotes, - SwapQuote, -} from "../types"; + addMarkupToAmount, +} from "../_utils"; +import { CrossSwap, CrossSwapQuotes, QuoteFetchStrategy } from "./types"; import { buildExactOutputBridgeTokenMessage, buildMinOutputBridgeTokenMessage, - getFallbackRecipient, -} from "../utils"; -import { AMOUNT_TYPE } from "../cross-swap"; -import { UniswapQuoteFetchStrategy, addMarkupToAmount } from "./utils"; + getCrossSwapType, + getQuoteFetchStrategy, + PREFERRED_BRIDGE_TOKENS, + QuoteFetchStrategies, +} from "./utils"; +import { getMultiCallHandlerAddress } from "../_multicall-handler"; +import { + defaultQuoteFetchStrategy, + AMOUNT_TYPE, + CROSS_SWAP_TYPE, + buildDestinationSwapCrossChainMessage, + assertMinOutputAmount, +} from "./utils"; const indicativeQuoteBuffer = 0.005; // 0.5% buffer for indicative quotes -/** - * Returns Uniswap v3 quote for a swap based on the output amount (exact or minimal) for - * route BRIDGEABLE input token -> ANY output token, e.g. USDC -> ARB. Required steps: - * 1. Get destination swap quote for bridgeable output token -> any token - * 2. Get bridge quote for bridgeable input token -> bridgeable output token - * @param crossSwap - Cross swap params - * @param strategy - Uniswap quote fetch strategy - */ -export async function getUniswapCrossSwapQuotesForOutputB2A( +export async function getCrossSwapQuotes( crossSwap: CrossSwap, - strategy: UniswapQuoteFetchStrategy + strategies: QuoteFetchStrategies = { + default: defaultQuoteFetchStrategy, + } +): Promise { + if (crossSwap.type === AMOUNT_TYPE.EXACT_INPUT) { + // @TODO: Add support for exact input amount + throw new Error("Not implemented yet"); + } + + if ( + crossSwap.type === AMOUNT_TYPE.MIN_OUTPUT || + crossSwap.type === AMOUNT_TYPE.EXACT_OUTPUT + ) { + return getCrossSwapQuotesForOutput(crossSwap, strategies); + } + + throw new Error("Invalid amount type"); +} + +async function getCrossSwapQuotesForOutput( + crossSwap: CrossSwap, + strategies: QuoteFetchStrategies +) { + const crossSwapType = getCrossSwapType({ + inputToken: crossSwap.inputToken.address, + originChainId: crossSwap.inputToken.chainId, + outputToken: crossSwap.outputToken.address, + destinationChainId: crossSwap.outputToken.chainId, + isInputNative: Boolean(crossSwap.isInputNative), + }); + + const typeToHandler = { + [CROSS_SWAP_TYPE.BRIDGEABLE_TO_BRIDGEABLE]: getCrossSwapQuotesForOutputB2B, + [CROSS_SWAP_TYPE.BRIDGEABLE_TO_ANY]: getCrossSwapQuotesForOutputB2A, + [CROSS_SWAP_TYPE.ANY_TO_BRIDGEABLE]: getCrossSwapQuotesForOutputA2B, + [CROSS_SWAP_TYPE.ANY_TO_ANY]: getCrossSwapQuotesForOutputA2A, + }; + + const handler = typeToHandler[crossSwapType]; + + if (!handler) { + throw new Error("Invalid cross swap type"); + } + + return handler(crossSwap, strategies); +} + +// @TODO: Implement the following function +export async function getCrossSwapQuotesForExactInput(crossSwap: CrossSwap) { + throw new Error("Not implemented yet"); +} + +export async function getCrossSwapQuotesForOutputB2B( + crossSwap: CrossSwap, + strategies: QuoteFetchStrategies +) { + const originStrategy = getQuoteFetchStrategy( + crossSwap.inputToken.chainId, + strategies + ); + const bridgeQuote = await getBridgeQuoteForMinOutput({ + inputToken: crossSwap.inputToken, + outputToken: crossSwap.outputToken, + minOutputAmount: crossSwap.amount, + recipient: getMultiCallHandlerAddress(crossSwap.outputToken.chainId), + 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, + bridgeQuote, + originSwapQuote: undefined, + contracts: { + depositEntryPoint: originStrategy.getOriginEntryPoints( + crossSwap.inputToken.chainId + ).deposit, + }, + }; +} + +export async function getCrossSwapQuotesForOutputB2A( + crossSwap: CrossSwap, + strategies: QuoteFetchStrategies ): Promise { const profiler = new Profiler({ - at: "api/_dexes/uniswap/quote-resolver#getUniswapCrossSwapQuotesForOutputB2A", + at: "api/_dexes/cross-swap-service#getCrossSwapQuotesForOutputB2A", logger: console, }); + const originStrategy = getQuoteFetchStrategy( + crossSwap.inputToken.chainId, + strategies + ); + const destinationStrategy = getQuoteFetchStrategy( + crossSwap.outputToken.chainId, + strategies + ); + const originSwapChainId = crossSwap.inputToken.chainId; const destinationSwapChainId = crossSwap.outputToken.chainId; const bridgeRoute = getRouteByInputTokenAndDestinationChain( crossSwap.inputToken.address, @@ -88,10 +178,17 @@ export async function getUniswapCrossSwapQuotesForOutputB2A( slippageTolerance: crossSwap.slippageTolerance, type: crossSwap.type, }; + const originRouter = originStrategy.getRouter(originSwapChainId); + const destinationRouter = destinationStrategy.getRouter( + destinationSwapChainId + ); + const depositEntryPoint = + originStrategy.getOriginEntryPoints(originSwapChainId).deposit; + // 1. Get INDICATIVE destination swap quote for bridgeable output token -> any token // with exact output amount. This request is faster but does not contain calldata. const indicativeDestinationSwapQuote = await profiler.measureAsync( - strategy.fetchFn( + destinationStrategy.fetchFn( { ...destinationSwap, amount: crossSwap.amount.toString(), @@ -109,7 +206,7 @@ export async function getUniswapCrossSwapQuotesForOutputB2A( Promise.all([ // 2.1. REAL destination swap quote for bridgeable output token -> any token. // Quote contains calldata. - strategy.fetchFn( + destinationStrategy.fetchFn( { ...destinationSwap, amount: crossSwap.amount.toString(), @@ -127,7 +224,7 @@ export async function getUniswapCrossSwapQuotesForOutputB2A( crossSwap, destinationSwapQuote: indicativeDestinationSwapQuote, bridgeableOutputToken, - routerAddress: strategy.getRouterAddress(destinationSwapChainId), + routerAddress: destinationRouter.address, }), }), ]), @@ -143,7 +240,7 @@ export async function getUniswapCrossSwapQuotesForOutputB2A( crossSwap, destinationSwapQuote, bridgeableOutputToken, - routerAddress: strategy.getRouterAddress(destinationSwapChainId), + routerAddress: destinationRouter.address, }); return { @@ -151,24 +248,24 @@ export async function getUniswapCrossSwapQuotesForOutputB2A( bridgeQuote, destinationSwapQuote, originSwapQuote: undefined, + contracts: { + originRouter, + destinationRouter, + depositEntryPoint, + }, }; } -/** - * Returns Uniswap v3 quote for a swap with min. output amount for route - * ANY input token -> BRIDGEABLE output token, e.g. ARB -> USDC. Required steps: - * 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 getUniswapCrossSwapQuotesForOutputA2B( +export async function getCrossSwapQuotesForOutputA2B( crossSwap: CrossSwap, - strategy: UniswapQuoteFetchStrategy -): Promise { + strategies: QuoteFetchStrategies +) { const profiler = new Profiler({ - at: "api/_dexes/uniswap/quote-resolver#getUniswapCrossSwapQuotesForOutputA2B", + at: "api/_dexes/cross-swap-service#getCrossSwapQuotesForOutputA2B", logger: console, }); const originSwapChainId = crossSwap.inputToken.chainId; + const originStrategy = getQuoteFetchStrategy(originSwapChainId, strategies); const destinationChainId = crossSwap.outputToken.chainId; const bridgeRoute = getRouteByOutputTokenAndOriginChain( crossSwap.outputToken.address, @@ -220,7 +317,7 @@ export async function getUniswapCrossSwapQuotesForOutputA2B( } const originSwapEntryPoint = - strategy.getOriginSwapEntryPoint(originSwapChainId); + originStrategy.getOriginEntryPoints(originSwapChainId).swapAndBridge; const originSwap = { chainId: originSwapChainId, tokenIn: crossSwap.inputToken, @@ -231,7 +328,7 @@ export async function getUniswapCrossSwapQuotesForOutputA2B( }; // 2.1. Get origin swap quote for any input token -> bridgeable input token const originSwapQuote = await profiler.measureAsync( - strategy.fetchFn( + originStrategy.fetchFn( { ...originSwap, amount: bridgeQuote.inputAmount.toString(), @@ -246,7 +343,7 @@ export async function getUniswapCrossSwapQuotesForOutputA2B( // 2.2. Re-fetch origin swap quote with updated input amount and EXACT_INPUT type. // This prevents leftover tokens in the SwapAndBridge contract. let adjOriginSwapQuote = await profiler.measureAsync( - strategy.fetchFn( + originStrategy.fetchFn( { ...originSwap, amount: addMarkupToAmount( @@ -267,32 +364,30 @@ export async function getUniswapCrossSwapQuotesForOutputA2B( crossSwap, bridgeQuote, destinationSwapQuote: undefined, - originSwapQuote: { - ...adjOriginSwapQuote, - entryPointContract: originSwapEntryPoint, + originSwapQuote, + contracts: { + originSwapEntryPoint, + depositEntryPoint: + originStrategy.getOriginEntryPoints(originSwapChainId).deposit, + originRouter: originStrategy.getRouter(originSwapChainId), }, }; } -/** - * Returns Uniswap v3 quote for a swap with min. output amount for route - * ANY input token -> ANY output token, e.g. ARB -> OP. We compare quotes from - * different bridge routes and return the best one. In this iteration, we only - * consider a hardcoded list of high-liquid bridge routes. - * @param crossSwap - * @param opts - */ -export async function getBestUniswapCrossSwapQuotesForOutputA2A( +export async function getCrossSwapQuotesForOutputA2A( crossSwap: CrossSwap, - originStrategy: UniswapQuoteFetchStrategy, - destinationStrategy: UniswapQuoteFetchStrategy, - opts: { - preferredBridgeTokens: string[]; - bridgeRoutesLimit: number; - } -): Promise { + strategies: QuoteFetchStrategies +) { + const preferredBridgeTokens = PREFERRED_BRIDGE_TOKENS; + const bridgeRoutesLimit = 1; + const originSwapChainId = crossSwap.inputToken.chainId; const destinationSwapChainId = crossSwap.outputToken.chainId; + const originStrategy = getQuoteFetchStrategy(originSwapChainId, strategies); + const destinationStrategy = getQuoteFetchStrategy( + destinationSwapChainId, + strategies + ); const allBridgeRoutes = getRoutesByChainIds( originSwapChainId, destinationSwapChainId @@ -305,11 +400,11 @@ export async function getBestUniswapCrossSwapQuotesForOutputA2A( } const preferredBridgeRoutes = allBridgeRoutes.filter(({ toTokenSymbol }) => - opts.preferredBridgeTokens.includes(toTokenSymbol) + preferredBridgeTokens.includes(toTokenSymbol) ); const bridgeRoutesToCompare = ( preferredBridgeRoutes.length > 0 ? preferredBridgeRoutes : allBridgeRoutes - ).slice(0, opts.bridgeRoutesLimit); + ).slice(0, bridgeRoutesLimit); if (bridgeRoutesToCompare.length === 0) { throw new Error( @@ -319,7 +414,7 @@ export async function getBestUniswapCrossSwapQuotesForOutputA2A( const crossSwapQuotes = await Promise.all( bridgeRoutesToCompare.map((bridgeRoute) => - getUniswapCrossSwapQuotesForOutputA2A( + getCrossSwapQuotesForOutputByRouteA2A( crossSwap, bridgeRoute, originStrategy, @@ -339,13 +434,7 @@ export async function getBestUniswapCrossSwapQuotesForOutputA2A( return bestCrossSwapQuote; } -/** - * Returns Uniswap v3 quote for a swap with min. output amount for route - * ANY input token -> ANY output token, e.g. ARB -> OP, using a specific bridge route. - * @param crossSwap - * @param bridgeRoute - */ -export async function getUniswapCrossSwapQuotesForOutputA2A( +export async function getCrossSwapQuotesForOutputByRouteA2A( crossSwap: CrossSwap, bridgeRoute: { fromTokenAddress: string; @@ -353,11 +442,11 @@ export async function getUniswapCrossSwapQuotesForOutputA2A( toTokenAddress: string; toChain: number; }, - originStrategy: UniswapQuoteFetchStrategy, - destinationStrategy: UniswapQuoteFetchStrategy + originStrategy: QuoteFetchStrategy, + destinationStrategy: QuoteFetchStrategy ): Promise { const profiler = new Profiler({ - at: "api/_dexes/uniswap/quote-resolver#getUniswapCrossSwapQuotesForOutputA2A", + at: "api/_dexes/cross-swap-service#getCrossSwapQuotesForOutputByRouteA2A", logger: console, }); const originSwapChainId = crossSwap.inputToken.chainId; @@ -400,7 +489,13 @@ export async function getUniswapCrossSwapQuotesForOutputA2A( destinationSwapChainId ); const originSwapEntryPoint = - originStrategy.getOriginSwapEntryPoint(originSwapChainId); + originStrategy.getOriginEntryPoints(originSwapChainId).swapAndBridge; + const depositEntryPoint = + originStrategy.getOriginEntryPoints(originSwapChainId).deposit; + const originRouter = originStrategy.getRouter(originSwapChainId); + const destinationRouter = destinationStrategy.getRouter( + destinationSwapChainId + ); const originSwap = { chainId: originSwapChainId, tokenIn: crossSwap.inputToken, @@ -449,9 +544,7 @@ export async function getUniswapCrossSwapQuotesForOutputA2A( crossSwap, destinationSwapQuote: indicativeDestinationSwapQuote, bridgeableOutputToken, - routerAddress: destinationStrategy.getRouterAddress( - destinationSwapChainId - ), + routerAddress: destinationRouter.address, }), }), "INDICATIVE_getBridgeQuote" @@ -493,9 +586,7 @@ export async function getUniswapCrossSwapQuotesForOutputA2A( crossSwap, destinationSwapQuote: indicativeDestinationSwapQuote, bridgeableOutputToken, - routerAddress: destinationStrategy.getRouterAddress( - destinationSwapChainId - ), + routerAddress: destinationRouter.address, }), }), originStrategy.fetchFn( @@ -522,141 +613,19 @@ export async function getUniswapCrossSwapQuotesForOutputA2A( crossSwap, destinationSwapQuote, bridgeableOutputToken, - routerAddress: destinationStrategy.getRouterAddress(destinationSwapChainId), + routerAddress: destinationRouter.address, }); return { crossSwap, destinationSwapQuote, bridgeQuote, - originSwapQuote: { - ...originSwapQuote, - entryPointContract: originSwapEntryPoint, + originSwapQuote, + contracts: { + originSwapEntryPoint, + depositEntryPoint, + originRouter: originRouter, + destinationRouter: destinationRouter, }, }; } - -function buildDestinationSwapCrossChainMessage({ - crossSwap, - destinationSwapQuote, - bridgeableOutputToken, - routerAddress, -}: { - crossSwap: CrossSwap; - bridgeableOutputToken: AcrossToken; - destinationSwapQuote: SwapQuote; - routerAddress: string; -}) { - const destinationSwapChainId = destinationSwapQuote.tokenOut.chainId; - const isIndicativeQuote = - destinationSwapQuote.swapTx.to === "0x0" && - destinationSwapQuote.swapTx.data === "0x0" && - destinationSwapQuote.swapTx.value === "0x0"; - - let transferActions: { - target: string; - callData: string; - value: string; - }[] = []; - - // 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. - if (crossSwap.isOutputNative) { - transferActions = [ - { - target: crossSwap.outputToken.address, - callData: encodeWethWithdrawCalldata(crossSwap.amount), - value: "0", - }, - { - target: crossSwap.recipient, - callData: "0x", - value: crossSwap.amount.toString(), - }, - ]; - } - // If output token is an ERC-20 token and amount type is EXACT_OUTPUT, we need - // to transfer the EXACT output amount to the recipient. The refundAddress / depositor - // will receive any leftovers. - else if (crossSwap.type === AMOUNT_TYPE.EXACT_OUTPUT) { - transferActions = [ - { - target: crossSwap.outputToken.address, - callData: encodeTransferCalldata(crossSwap.recipient, crossSwap.amount), - value: "0", - }, - { - target: getMultiCallHandlerAddress(destinationSwapChainId), - callData: encodeDrainCalldata( - crossSwap.outputToken.address, - crossSwap.refundAddress ?? crossSwap.depositor - ), - value: "0", - }, - ]; - } - // If output token is an ERC-20 token and amount type is MIN_OUTPUT, we need - // to transfer all realized output tokens to the recipient. - else if (crossSwap.type === AMOUNT_TYPE.MIN_OUTPUT) { - transferActions = [ - { - target: getMultiCallHandlerAddress(destinationSwapChainId), - callData: encodeDrainCalldata( - crossSwap.outputToken.address, - crossSwap.recipient - ), - value: "0", - }, - ]; - } - - const swapActions = isIndicativeQuote - ? [] - : [ - { - target: destinationSwapQuote.swapTx.to, - callData: destinationSwapQuote.swapTx.data, - value: destinationSwapQuote.swapTx.value, - }, - ]; - - return buildMulticallHandlerMessage({ - fallbackRecipient: getFallbackRecipient(crossSwap), - actions: [ - // approve bridgeable output token - { - target: bridgeableOutputToken.address, - callData: encodeApproveCalldata( - routerAddress, - destinationSwapQuote.maximumAmountIn - ), - value: "0", - }, - // swap bridgeable output token -> cross swap output token - ...swapActions, - // transfer output tokens to recipient - ...transferActions, - // drain remaining bridgeable output tokens from MultiCallHandler contract - { - target: getMultiCallHandlerAddress(destinationSwapChainId), - callData: encodeDrainCalldata( - bridgeableOutputToken.address, - crossSwap.refundAddress ?? crossSwap.depositor - ), - value: "0", - }, - ], - }); -} - -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/_dexes/cross-swap.ts b/api/_dexes/cross-swap.ts deleted file mode 100644 index fed297979..000000000 --- a/api/_dexes/cross-swap.ts +++ /dev/null @@ -1,392 +0,0 @@ -import { SpokePool } from "@across-protocol/contracts/dist/typechain"; -import { PopulatedTransaction } from "ethers"; -import { utils } from "@across-protocol/sdk"; - -import { - isRouteEnabled, - isInputTokenBridgeable, - isOutputTokenBridgeable, - getBridgeQuoteForMinOutput, - getSpokePool, -} from "../_utils"; -import { - getBestUniswapCrossSwapQuotesForOutputA2A, - getUniswapCrossSwapQuotesForOutputA2B, - getUniswapCrossSwapQuotesForOutputB2A, -} from "./uniswap/quote-resolver"; -import { getSwapRouter02Strategy } from "./uniswap/swap-router-02"; -import { UniswapQuoteFetchStrategy } from "./uniswap/utils"; -import { CrossSwap, CrossSwapQuotes } from "./types"; -import { - buildExactOutputBridgeTokenMessage, - buildMinOutputBridgeTokenMessage, - getUniversalSwapAndBridge, -} from "./utils"; -import { getSpokePoolPeriphery } from "../_spoke-pool-periphery"; -import { tagIntegratorId } from "../_integrator-id"; -import { getMultiCallHandlerAddress } from "../_multicall-handler"; -import { CHAIN_IDs } from "../_constants"; -import { getPermitTypedData } from "../_permit"; - -export type CrossSwapType = - (typeof CROSS_SWAP_TYPE)[keyof typeof CROSS_SWAP_TYPE]; - -export type AmountType = (typeof AMOUNT_TYPE)[keyof typeof AMOUNT_TYPE]; - -export const AMOUNT_TYPE = { - EXACT_INPUT: "exactInput", - EXACT_OUTPUT: "exactOutput", - MIN_OUTPUT: "minOutput", -} as const; - -export const CROSS_SWAP_TYPE = { - BRIDGEABLE_TO_BRIDGEABLE: "bridgeableToBridgeable", - BRIDGEABLE_TO_ANY: "bridgeableToAny", - ANY_TO_BRIDGEABLE: "anyToBridgeable", - ANY_TO_ANY: "anyToAny", -} as const; - -export const PREFERRED_BRIDGE_TOKENS = ["WETH"]; - -const defaultQuoteFetchStrategy: UniswapQuoteFetchStrategy = - // This will be our default strategy until the periphery contract is audited - getSwapRouter02Strategy("UniversalSwapAndBridge"); -const strategyOverrides = { - [CHAIN_IDs.BLAST]: defaultQuoteFetchStrategy, -}; - -export async function getCrossSwapQuotes( - crossSwap: CrossSwap -): Promise { - if (crossSwap.type === AMOUNT_TYPE.EXACT_INPUT) { - // @TODO: Add support for exact input amount - throw new Error("Not implemented yet"); - } - - 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 getCrossSwapQuotesForOutput(crossSwap: CrossSwap) { - const crossSwapType = getCrossSwapType({ - inputToken: crossSwap.inputToken.address, - originChainId: crossSwap.inputToken.chainId, - outputToken: crossSwap.outputToken.address, - destinationChainId: crossSwap.outputToken.chainId, - isInputNative: Boolean(crossSwap.isInputNative), - }); - - if (crossSwapType === CROSS_SWAP_TYPE.BRIDGEABLE_TO_BRIDGEABLE) { - return getCrossSwapQuotesForOutputB2B(crossSwap); - } - - if (crossSwapType === CROSS_SWAP_TYPE.BRIDGEABLE_TO_ANY) { - return getCrossSwapQuotesForOutputB2A(crossSwap); - } - - if (crossSwapType === CROSS_SWAP_TYPE.ANY_TO_BRIDGEABLE) { - return getCrossSwapQuotesForOutputA2B(crossSwap); - } - - if (crossSwapType === CROSS_SWAP_TYPE.ANY_TO_ANY) { - return getCrossSwapQuotesForOutputA2A(crossSwap); - } - - throw new Error("Invalid cross swap type"); -} - -// @TODO: Implement the following function -export async function getCrossSwapQuotesForExactInput(crossSwap: CrossSwap) { - throw new Error("Not implemented yet"); -} - -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: - 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, - bridgeQuote, - originSwapQuote: undefined, - }; -} - -export async function getCrossSwapQuotesForOutputB2A(crossSwap: CrossSwap) { - return getUniswapCrossSwapQuotesForOutputB2A( - crossSwap, - // Destination swap requires destination chain's quote fetch strategy - getQuoteFetchStrategy(crossSwap.outputToken.chainId) - ); -} - -export async function getCrossSwapQuotesForOutputA2B(crossSwap: CrossSwap) { - return getUniswapCrossSwapQuotesForOutputA2B( - crossSwap, - // Origin swap requires origin chain's quote fetch strategy - getQuoteFetchStrategy(crossSwap.inputToken.chainId) - ); -} - -export async function getCrossSwapQuotesForOutputA2A(crossSwap: CrossSwap) { - return getBestUniswapCrossSwapQuotesForOutputA2A( - crossSwap, - getQuoteFetchStrategy(crossSwap.inputToken.chainId), - getQuoteFetchStrategy(crossSwap.outputToken.chainId), - { - preferredBridgeTokens: PREFERRED_BRIDGE_TOKENS, - bridgeRoutesLimit: 1, - } - ); -} - -function getQuoteFetchStrategy(chainId: number) { - return strategyOverrides[chainId] ?? defaultQuoteFetchStrategy; -} - -export function getCrossSwapType(params: { - inputToken: string; - originChainId: number; - outputToken: string; - destinationChainId: number; - isInputNative: boolean; -}): CrossSwapType { - if ( - isRouteEnabled( - params.originChainId, - params.destinationChainId, - params.inputToken, - params.outputToken - ) - ) { - return CROSS_SWAP_TYPE.BRIDGEABLE_TO_BRIDGEABLE; - } - - // Prefer destination swap if input token is native because legacy - // `UniversalSwapAndBridge` does not support native tokens as input. - if (params.isInputNative) { - if (isInputTokenBridgeable(params.inputToken, params.originChainId)) { - return CROSS_SWAP_TYPE.BRIDGEABLE_TO_ANY; - } - // We can't bridge native tokens that are not ETH, e.g. MATIC or AZERO. Therefore - // throw until we have periphery contract audited so that it can accept native - // tokens and do an origin swap. - throw new Error( - "Unsupported swap: Input token is native but not bridgeable" - ); - } - - if (isOutputTokenBridgeable(params.outputToken, params.destinationChainId)) { - return CROSS_SWAP_TYPE.ANY_TO_BRIDGEABLE; - } - - if (isInputTokenBridgeable(params.inputToken, params.originChainId)) { - return CROSS_SWAP_TYPE.BRIDGEABLE_TO_ANY; - } - - return CROSS_SWAP_TYPE.ANY_TO_ANY; -} - -export async function buildCrossSwapTxForAllowanceHolder( - crossSwapQuotes: CrossSwapQuotes, - integratorId?: string -) { - const originChainId = crossSwapQuotes.crossSwap.inputToken.chainId; - const spokePool = getSpokePool(originChainId); - - const deposit = await extractDepositDataStruct(crossSwapQuotes); - - let tx: PopulatedTransaction; - let toAddress: string; - - if (crossSwapQuotes.originSwapQuote) { - const { entryPointContract } = crossSwapQuotes.originSwapQuote; - if (entryPointContract.name === "SpokePoolPeriphery") { - const spokePoolPeriphery = getSpokePoolPeriphery( - entryPointContract.address, - originChainId - ); - tx = await spokePoolPeriphery.populateTransaction.swapAndBridge( - crossSwapQuotes.originSwapQuote.tokenIn.address, - crossSwapQuotes.originSwapQuote.tokenOut.address, - crossSwapQuotes.originSwapQuote.swapTx.data, - crossSwapQuotes.originSwapQuote.maximumAmountIn, - crossSwapQuotes.originSwapQuote.minAmountOut, - deposit, - { - value: crossSwapQuotes.crossSwap.isInputNative - ? crossSwapQuotes.originSwapQuote.maximumAmountIn - : 0, - } - ); - toAddress = spokePoolPeriphery.address; - } else if (entryPointContract.name === "UniversalSwapAndBridge") { - const universalSwapAndBridge = getUniversalSwapAndBridge( - entryPointContract.dex, - originChainId - ); - tx = await universalSwapAndBridge.populateTransaction.swapAndBridge( - crossSwapQuotes.originSwapQuote.tokenIn.address, - crossSwapQuotes.originSwapQuote.tokenOut.address, - crossSwapQuotes.originSwapQuote.swapTx.data, - crossSwapQuotes.originSwapQuote.maximumAmountIn, - crossSwapQuotes.originSwapQuote.minAmountOut, - deposit - ); - toAddress = universalSwapAndBridge.address; - } else { - throw new Error( - `Could not build cross swap tx for unknown entry point contract` - ); - } - } else { - 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, - { - value: crossSwapQuotes.crossSwap.isInputNative - ? deposit.inputAmount - : 0, - } - ); - toAddress = spokePool.address; - } - - return { - from: crossSwapQuotes.crossSwap.depositor, - to: toAddress, - data: integratorId ? tagIntegratorId(integratorId, tx.data!) : tx.data, - value: tx.value, - }; -} - -export async function getCrossSwapTxForPermit( - crossSwapQuotes: CrossSwapQuotes, - permitDeadline: number -) { - const originChainId = crossSwapQuotes.crossSwap.inputToken.chainId; - const deposit = await extractDepositDataStruct(crossSwapQuotes); - - let methodName: string; - let argsWithoutSignature: Record; - if (crossSwapQuotes.originSwapQuote) { - const spokePoolPeriphery = crossSwapQuotes.originSwapQuote.peripheryAddress; - methodName = "swapAndBridgeWithPermit"; - argsWithoutSignature = { - swapToken: crossSwapQuotes.originSwapQuote.tokenIn.address, - acrossInputToken: crossSwapQuotes.originSwapQuote.tokenOut.address, - routerCalldata: crossSwapQuotes.originSwapQuote.swapTx.data, - swapTokenAmount: crossSwapQuotes.originSwapQuote.maximumAmountIn, - minExpectedInputTokenAmount: crossSwapQuotes.originSwapQuote.minAmountOut, - depositData: deposit, - deadline: permitDeadline, - }; - } else { - methodName = "depositWithPermit"; - argsWithoutSignature = { - acrossInputToken: crossSwapQuotes.bridgeQuote.inputToken.address, - acrossInputAmount: crossSwapQuotes.bridgeQuote.inputAmount, - depositData: deposit, - deadline: permitDeadline, - }; - } - - const permitTypedData = await getPermitTypedData({ - tokenAddress: - crossSwapQuotes.originSwapQuote?.tokenIn.address || - crossSwapQuotes.bridgeQuote.inputToken.address, - chainId: originChainId, - ownerAddress: crossSwapQuotes.crossSwap.depositor, - spenderAddress: spokePoolPeriphery.address, - value: - crossSwapQuotes.originSwapQuote?.maximumAmountIn || - crossSwapQuotes.bridgeQuote.inputAmount, - deadline: permitDeadline, - }); - return { - permit: { - eip712: permitTypedData.eip712, - }, - swapTx: { - chainId: originChainId, - to: spokePoolPeriphery.address, - methodName, - argsWithoutSignature, - }, - }; -} - -async function extractDepositDataStruct(crossSwapQuotes: CrossSwapQuotes) { - const originChainId = crossSwapQuotes.crossSwap.inputToken.chainId; - const destinationChainId = crossSwapQuotes.crossSwap.outputToken.chainId; - const spokePool = getSpokePool(originChainId); - const message = crossSwapQuotes.bridgeQuote.message || "0x"; - const refundAddress = - crossSwapQuotes.crossSwap.refundAddress ?? - crossSwapQuotes.crossSwap.depositor; - const deposit = { - depositor: crossSwapQuotes.crossSwap.refundOnOrigin - ? refundAddress - : crossSwapQuotes.crossSwap.depositor, - recipient: utils.isMessageEmpty(message) - ? crossSwapQuotes.crossSwap.recipient - : getMultiCallHandlerAddress(destinationChainId), - inputToken: crossSwapQuotes.bridgeQuote.inputToken.address, - outputToken: crossSwapQuotes.bridgeQuote.outputToken.address, - inputAmount: crossSwapQuotes.bridgeQuote.inputAmount, - outputAmount: crossSwapQuotes.bridgeQuote.outputAmount, - destinationChainid: destinationChainId, - exclusiveRelayer: - crossSwapQuotes.bridgeQuote.suggestedFees.exclusiveRelayer, - quoteTimestamp: crossSwapQuotes.bridgeQuote.suggestedFees.timestamp, - fillDeadline: await getFillDeadline(spokePool), - exclusivityDeadline: - crossSwapQuotes.bridgeQuote.suggestedFees.exclusivityDeadline, - exclusivityParameter: - crossSwapQuotes.bridgeQuote.suggestedFees.exclusivityDeadline, - message, - }; - return deposit; -} - -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 e69fc1418..890bcac60 100644 --- a/api/_dexes/types.ts +++ b/api/_dexes/types.ts @@ -1,6 +1,8 @@ import { BigNumber } from "ethers"; +import { TradeType } from "@uniswap/sdk-core"; + import { getSuggestedFees } from "../_utils"; -import { AmountType, CrossSwapType } from "./cross-swap"; +import { AmountType, CrossSwapType } from "./utils"; export type { AmountType, CrossSwapType }; @@ -75,14 +77,18 @@ export type CrossSwapQuotes = { suggestedFees: Awaited>; }; destinationSwapQuote?: SwapQuote; - originSwapQuote?: SwapQuote & { - entryPointContract: OriginSwapEntryPointContract; + originSwapQuote?: SwapQuote; + contracts: { + depositEntryPoint: DepositEntryPointContract; + originRouter?: RouterContract; + destinationRouter?: RouterContract; + originSwapEntryPoint?: OriginSwapEntryPointContract; }; }; export type OriginSwapEntryPointContract = | { - name: "SpokePoolPeriphery"; + name: "SpokePoolPeripheryProxy" | "SpokePoolPeriphery"; address: string; } | { @@ -90,14 +96,53 @@ export type OriginSwapEntryPointContract = address: string; dex: SupportedDex; }; +export type DepositEntryPointContract = { + name: "SpokePoolPeriphery" | "SpokePool"; + address: string; +}; +export type RouterContract = { + name: string; + address: string; +}; export type CrossSwapQuotesWithFees = CrossSwapQuotes & { fees: CrossSwapFees; }; -// { currency => amount } export type CrossSwapFees = { bridgeFees: Record; originSwapFees?: Record; destinationSwapFees?: Record; }; + +export type QuoteFetchStrategy = { + getRouter: (chainId: number) => { + address: string; + name: string; + }; + getOriginEntryPoints: (chainId: number) => { + swapAndBridge: + | { + name: "UniversalSwapAndBridge"; + address: string; + dex: "uniswap" | "1inch"; + } + | { + name: "SpokePoolPeripheryProxy" | "SpokePoolPeriphery"; + address: string; + }; + deposit: { + name: "SpokePoolPeriphery" | "SpokePool"; + address: string; + }; + }; + fetchFn: QuoteFetchFn; +}; + +export type QuoteFetchFn = ( + swap: Swap, + tradeType: TradeType, + opts?: Partial<{ + useIndicativeQuote: boolean; + }> +) => Promise; diff --git a/api/_dexes/uniswap/swap-quoter.ts b/api/_dexes/uniswap/swap-quoter.ts index f22768cae..7d3458a3e 100644 --- a/api/_dexes/uniswap/swap-quoter.ts +++ b/api/_dexes/uniswap/swap-quoter.ts @@ -22,7 +22,7 @@ import { CHAIN_IDs } from "@across-protocol/constants"; import { utils } from "@across-protocol/sdk"; import { Swap } from "../types"; -import { getSwapAndBridgeAddress } from "../utils"; +import { getSwapAndBridgeAddress } from "../../_swap-and-bridge"; import { getProdToken } from "./utils"; import { callViaMulticall3, getProvider } from "../../_utils"; diff --git a/api/_dexes/uniswap/swap-router-02.ts b/api/_dexes/uniswap/swap-router-02.ts index 841617628..534cd52ae 100644 --- a/api/_dexes/uniswap/swap-router-02.ts +++ b/api/_dexes/uniswap/swap-router-02.ts @@ -4,21 +4,24 @@ import { SwapRouter } from "@uniswap/router-sdk"; import { CHAIN_IDs } from "@across-protocol/constants"; -import { getLogger } from "../../_utils"; -import { OriginSwapEntryPointContract, Swap, SwapQuote } from "../types"; -import { getSpokePoolPeripheryAddress } from "../../_spoke-pool-periphery"; import { + getLogger, + getSpokePoolAddress, addMarkupToAmount, - floatToPercent, - UniswapQuoteFetchStrategy, -} from "./utils"; +} from "../../_utils"; +import { Swap, SwapQuote } from "../types"; +import { + getSpokePoolPeripheryAddress, + getSpokePoolPeripheryProxyAddress, +} from "../../_spoke-pool-periphery"; +import { getUniversalSwapAndBridgeAddress } from "../../_swap-and-bridge"; +import { floatToPercent, UniswapQuoteFetchStrategy } from "./utils"; import { getUniswapClassicQuoteFromApi, getUniswapClassicIndicativeQuoteFromApi, UniswapClassicQuoteFromApi, } from "./trading-api"; import { RouterTradeAdapter } from "./adapter"; -import { getUniversalSwapAndBridgeAddress } from "../utils"; import { buildCacheKey, makeCacheGetterAndSetter } from "../../_cache"; // Taken from here: https://docs.uniswap.org/contracts/v3/reference/deployments/ @@ -35,20 +38,51 @@ export const SWAP_ROUTER_02_ADDRESS = { }; export function getSwapRouter02Strategy( - originSwapEntryPointContractName: OriginSwapEntryPointContract["name"] + originSwapEntryPointContractName: + | "SpokePoolPeriphery" + | "SpokePoolPeripheryProxy" + | "UniversalSwapAndBridge" ): UniswapQuoteFetchStrategy { - const getRouterAddress = (chainId: number) => SWAP_ROUTER_02_ADDRESS[chainId]; - const getOriginSwapEntryPoint = (chainId: number) => { - if (originSwapEntryPointContractName === "SpokePoolPeriphery") { + const getRouter = (chainId: number) => { + return { + address: SWAP_ROUTER_02_ADDRESS[chainId], + name: "UniswapV3SwapRouter02", + }; + }; + const getOriginEntryPoints = (chainId: number) => { + if (originSwapEntryPointContractName === "SpokePoolPeripheryProxy") { + return { + swapAndBridge: { + name: "SpokePoolPeripheryProxy", + address: getSpokePoolPeripheryProxyAddress(chainId), + }, + deposit: { + name: "SpokePoolPeriphery", + address: getSpokePoolPeripheryAddress(chainId), + }, + } as const; + } else if (originSwapEntryPointContractName === "SpokePoolPeriphery") { return { - name: "SpokePoolPeriphery", - address: getSpokePoolPeripheryAddress("uniswap-swapRouter02", chainId), + swapAndBridge: { + name: "SpokePoolPeriphery", + address: getSpokePoolPeripheryAddress(chainId), + }, + deposit: { + name: "SpokePoolPeriphery", + address: getSpokePoolPeripheryAddress(chainId), + }, } as const; } else if (originSwapEntryPointContractName === "UniversalSwapAndBridge") { return { - name: "UniversalSwapAndBridge", - address: getUniversalSwapAndBridgeAddress("uniswap", chainId), - dex: "uniswap", + swapAndBridge: { + name: "UniversalSwapAndBridge", + address: getUniversalSwapAndBridgeAddress("uniswap", chainId), + dex: "uniswap", + }, + deposit: { + name: "SpokePool", + address: getSpokePoolAddress(chainId), + }, } as const; } throw new Error( @@ -167,8 +201,8 @@ export function getSwapRouter02Strategy( }; return { - getRouterAddress, - getOriginSwapEntryPoint, + getRouter, + getOriginEntryPoints, fetchFn, }; } diff --git a/api/_dexes/uniswap/universal-router.ts b/api/_dexes/uniswap/universal-router.ts index bbaad27df..5e24376d4 100644 --- a/api/_dexes/uniswap/universal-router.ts +++ b/api/_dexes/uniswap/universal-router.ts @@ -3,19 +3,18 @@ import { TradeType } from "@uniswap/sdk-core"; import { CHAIN_IDs } from "@across-protocol/constants"; import { SwapRouter } from "@uniswap/universal-router-sdk"; -import { getLogger } from "../../_utils"; +import { getLogger, addMarkupToAmount } from "../../_utils"; import { Swap, SwapQuote } from "../types"; -import { getSpokePoolPeripheryAddress } from "../../_spoke-pool-periphery"; +import { + getSpokePoolPeripheryAddress, + getSpokePoolPeripheryProxyAddress, +} from "../../_spoke-pool-periphery"; import { getUniswapClassicQuoteFromApi, getUniswapClassicIndicativeQuoteFromApi, UniswapClassicQuoteFromApi, } from "./trading-api"; -import { - UniswapQuoteFetchStrategy, - addMarkupToAmount, - floatToPercent, -} from "./utils"; +import { UniswapQuoteFetchStrategy, floatToPercent } from "./utils"; import { RouterTradeAdapter } from "./adapter"; // https://uniswap-docs.readme.io/reference/faqs#i-need-to-whitelist-the-router-addresses-where-can-i-find-them @@ -32,12 +31,23 @@ export const UNIVERSAL_ROUTER_ADDRESS = { }; export function getUniversalRouterStrategy(): UniswapQuoteFetchStrategy { - const getRouterAddress = (chainId: number) => - UNIVERSAL_ROUTER_ADDRESS[chainId]; - const getOriginSwapEntryPoint = (chainId: number) => + const getRouter = (chainId: number) => { + return { + address: UNIVERSAL_ROUTER_ADDRESS[chainId], + name: "UniswapV3UniversalRouter", + }; + }; + + const getOriginEntryPoints = (chainId: number) => ({ - name: "SpokePoolPeriphery", - address: getSpokePoolPeripheryAddress("uniswap-universalRouter", chainId), + swapAndBridge: { + name: "SpokePoolPeripheryProxy", + address: getSpokePoolPeripheryProxyAddress(chainId), + }, + deposit: { + name: "SpokePoolPeriphery", + address: getSpokePoolPeripheryAddress(chainId), + }, }) as const; const fetchFn = async ( @@ -129,8 +139,8 @@ export function getUniversalRouterStrategy(): UniswapQuoteFetchStrategy { }; return { - getRouterAddress, - getOriginSwapEntryPoint, + getRouter, + getOriginEntryPoints, fetchFn, }; } diff --git a/api/_dexes/uniswap/utils.ts b/api/_dexes/uniswap/utils.ts index 2840ff4cb..9699a7adc 100644 --- a/api/_dexes/uniswap/utils.ts +++ b/api/_dexes/uniswap/utils.ts @@ -1,32 +1,9 @@ import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; -import { Percent, TradeType } from "@uniswap/sdk-core"; -import axios from "axios"; -import { BigNumber, ethers } from "ethers"; -import { utils } from "@across-protocol/sdk"; +import { Percent } from "@uniswap/sdk-core"; -import { Swap, SwapQuote, Token } from "../types"; +import { Token, QuoteFetchStrategy } from "../types"; -export type UniswapQuoteFetchStrategy = { - getRouterAddress: (chainId: number) => string; - getOriginSwapEntryPoint: (chainId: number) => - | { - name: "SpokePoolPeriphery"; - address: string; - } - | { - name: "UniversalSwapAndBridge"; - address: string; - dex: "uniswap" | "1inch"; - }; - fetchFn: UniswapQuoteFetchFn; -}; -export type UniswapQuoteFetchFn = ( - swap: Swap, - tradeType: TradeType, - opts?: Partial<{ - useIndicativeQuote: boolean; - }> -) => Promise; +export type UniswapQuoteFetchStrategy = QuoteFetchStrategy; // Maps testnet chain IDs to their prod counterparts. Used to get the prod token // info for testnet tokens. @@ -44,57 +21,6 @@ export const UNISWAP_TRADING_API_BASE_URL = export const UNISWAP_API_KEY = process.env.UNISWAP_API_KEY || "JoyCGj29tT4pymvhaGciK4r1aIPvqW6W53xT1fwo"; -/** - * Based on https://uniswap-docs.readme.io/reference/aggregator_quote-1 - */ -export async function getUniswapClassicQuoteFromApi( - swap: Omit & { swapper: string }, - tradeType: TradeType -) { - const response = await axios.post( - `${UNISWAP_TRADING_API_BASE_URL}/quote`, - { - type: - tradeType === TradeType.EXACT_INPUT ? "EXACT_INPUT" : "EXACT_OUTPUT", - tokenInChainId: swap.tokenIn.chainId, - tokenOutChainId: swap.tokenOut.chainId, - tokenIn: swap.tokenIn.address, - tokenOut: swap.tokenOut.address, - swapper: swap.swapper, - slippageTolerance: swap.slippageTolerance, - amount: swap.amount, - routingPreference: "CLASSIC", - urgency: "urgent", - }, - { - headers: { - "x-api-key": UNISWAP_API_KEY, - }, - } - ); - return response.data as { - requestId: string; - routing: "CLASSIC"; - quote: { - chainId: number; - input: { - amount: string; - token: string; - }; - output: { - amount: string; - token: string; - recipient: string; - }; - swapper: string; - route: any[]; - slippage: number; - tradeType: "EXACT_OUTPUT" | "EXACT_INPUT"; - quoteId: string; - }; - }; -} - export function getProdToken(token: Token) { const prodChainId = TESTNET_TO_PROD[token.chainId] || token.chainId; @@ -115,12 +41,6 @@ export function getProdToken(token: Token) { }; } -export function addMarkupToAmount(amount: BigNumber, markup = 0.01) { - return amount - .mul(ethers.utils.parseEther((1 + Number(markup)).toString())) - .div(utils.fixedPointAdjustment); -} - export function floatToPercent(value: number) { return new Percent( // max. slippage decimals is 2 diff --git a/api/_dexes/utils.ts b/api/_dexes/utils.ts index d90f95664..f8d0675d6 100644 --- a/api/_dexes/utils.ts +++ b/api/_dexes/utils.ts @@ -1,119 +1,100 @@ -import { - SwapAndBridge__factory, - UniversalSwapAndBridge__factory, -} from "@across-protocol/contracts"; import { BigNumber, constants } from "ethers"; +import { utils } from "@across-protocol/sdk"; +import { SpokePool } from "@across-protocol/contracts/dist/typechain"; -import { ENABLED_ROUTES, getProvider } from "../_utils"; +import { getSwapRouter02Strategy } from "./uniswap/swap-router-02"; import { buildMulticallHandlerMessage, + encodeApproveCalldata, encodeDrainCalldata, encodeTransferCalldata, encodeWethWithdrawCalldata, getMultiCallHandlerAddress, } from "../_multicall-handler"; -import { CrossSwap } from "./types"; +import { + CrossSwap, + CrossSwapQuotes, + QuoteFetchStrategy, + SwapQuote, + Token, +} from "./types"; +import { + isInputTokenBridgeable, + isRouteEnabled, + isOutputTokenBridgeable, + getSpokePool, +} from "../_utils"; -type SwapAndBridgeType = "SwapAndBridge" | "UniversalSwapAndBridge"; +export type CrossSwapType = + (typeof CROSS_SWAP_TYPE)[keyof typeof CROSS_SWAP_TYPE]; -export class UnsupportedDex extends Error { - constructor(dex: string, type: SwapAndBridgeType) { - super(`DEX/Aggregator '${dex}' not supported for '${type}'`); - } -} +export type AmountType = (typeof AMOUNT_TYPE)[keyof typeof AMOUNT_TYPE]; -export class UnsupportedDexOnChain extends Error { - constructor(chainId: number, dex: string, type: SwapAndBridgeType) { - super( - `DEX/Aggregator '${dex}' not supported on chain ${chainId} for '${type}'` - ); - } -} +export type QuoteFetchStrategies = Partial<{ + default: QuoteFetchStrategy; + [chainId: number]: QuoteFetchStrategy; +}>; -export class NoSwapRouteError extends Error { - constructor(args: { - dex: string; - tokenInSymbol: string; - tokenOutSymbol: string; - chainId: number; - swapType: string; - }) { - super( - `No ${args.dex} swap route found for '${args.swapType}' ${args.tokenInSymbol} to ${args.tokenOutSymbol} on chain ${args.chainId}` - ); - } -} +export const AMOUNT_TYPE = { + EXACT_INPUT: "exactInput", + EXACT_OUTPUT: "exactOutput", + MIN_OUTPUT: "minOutput", +} as const; -export const swapAndBridgeDexes = Object.keys( - ENABLED_ROUTES.swapAndBridgeAddresses -); +export const CROSS_SWAP_TYPE = { + BRIDGEABLE_TO_BRIDGEABLE: "bridgeableToBridgeable", + BRIDGEABLE_TO_ANY: "bridgeableToAny", + ANY_TO_BRIDGEABLE: "anyToBridgeable", + ANY_TO_ANY: "anyToAny", +} as const; -export const universalSwapAndBridgeDexes = Object.keys( - ENABLED_ROUTES.universalSwapAndBridgeAddresses -); +export const PREFERRED_BRIDGE_TOKENS = ["WETH"]; -export function getSwapAndBridgeAddress(dex: string, chainId: number) { - if (!_isDexSupportedForSwapAndBridge(dex)) { - throw new UnsupportedDex(dex, "SwapAndBridge"); - } +export const defaultQuoteFetchStrategy: QuoteFetchStrategy = + // This will be our default strategy until the periphery contract is audited + getSwapRouter02Strategy("UniversalSwapAndBridge"); - const address = ( - ENABLED_ROUTES.swapAndBridgeAddresses[dex] as Record - )?.[chainId]; - if (!address) { - throw new UnsupportedDexOnChain(chainId, dex, "SwapAndBridge"); +export function getCrossSwapType(params: { + inputToken: string; + originChainId: number; + outputToken: string; + destinationChainId: number; + isInputNative: boolean; +}): CrossSwapType { + if ( + isRouteEnabled( + params.originChainId, + params.destinationChainId, + params.inputToken, + params.outputToken + ) + ) { + return CROSS_SWAP_TYPE.BRIDGEABLE_TO_BRIDGEABLE; } - return address; -} -export function getUniversalSwapAndBridgeAddress(dex: string, chainId: number) { - if (!_isDexSupportedForUniversalSwapAndBridge(dex)) { - throw new UnsupportedDex(dex, "UniversalSwapAndBridge"); + // Prefer destination swap if input token is native because legacy + // `UniversalSwapAndBridge` does not support native tokens as input. + if (params.isInputNative) { + if (isInputTokenBridgeable(params.inputToken, params.originChainId)) { + return CROSS_SWAP_TYPE.BRIDGEABLE_TO_ANY; + } + // We can't bridge native tokens that are not ETH, e.g. MATIC or AZERO. Therefore + // throw until we have periphery contract audited so that it can accept native + // tokens and do an origin swap. + throw new Error( + "Unsupported swap: Input token is native but not bridgeable" + ); } - const address = ( - ENABLED_ROUTES.universalSwapAndBridgeAddresses[dex] as Record< - string, - string - > - )?.[chainId]; - if (!address) { - throw new UnsupportedDexOnChain(chainId, dex, "UniversalSwapAndBridge"); + if (isOutputTokenBridgeable(params.outputToken, params.destinationChainId)) { + return CROSS_SWAP_TYPE.ANY_TO_BRIDGEABLE; } - return address; -} - -export function getSwapAndBridge(dex: string, chainId: number) { - const swapAndBridgeAddress = getSwapAndBridgeAddress(dex, chainId); - return SwapAndBridge__factory.connect( - swapAndBridgeAddress, - getProvider(chainId) - ); -} - -export function getUniversalSwapAndBridge(dex: string, chainId: number) { - const universalSwapAndBridgeAddress = getUniversalSwapAndBridgeAddress( - dex, - chainId - ); - - return UniversalSwapAndBridge__factory.connect( - universalSwapAndBridgeAddress, - getProvider(chainId) - ); -} - -function _isDexSupportedForSwapAndBridge( - dex: string -): dex is keyof typeof ENABLED_ROUTES.swapAndBridgeAddresses { - return swapAndBridgeDexes.includes(dex); -} + if (isInputTokenBridgeable(params.inputToken, params.originChainId)) { + return CROSS_SWAP_TYPE.BRIDGEABLE_TO_ANY; + } -function _isDexSupportedForUniversalSwapAndBridge( - dex: string -): dex is keyof typeof ENABLED_ROUTES.universalSwapAndBridgeAddresses { - return universalSwapAndBridgeDexes.includes(dex); + return CROSS_SWAP_TYPE.ANY_TO_ANY; } /** @@ -212,3 +193,181 @@ export function getFallbackRecipient(crossSwap: CrossSwap) { ? constants.AddressZero : crossSwap.refundAddress ?? crossSwap.depositor; } + +export async function extractDepositDataStruct( + crossSwapQuotes: CrossSwapQuotes +) { + const originChainId = crossSwapQuotes.crossSwap.inputToken.chainId; + const destinationChainId = crossSwapQuotes.crossSwap.outputToken.chainId; + const spokePool = getSpokePool(originChainId); + const message = crossSwapQuotes.bridgeQuote.message || "0x"; + const refundAddress = + crossSwapQuotes.crossSwap.refundAddress ?? + crossSwapQuotes.crossSwap.depositor; + const deposit = { + depositor: crossSwapQuotes.crossSwap.refundOnOrigin + ? refundAddress + : crossSwapQuotes.crossSwap.depositor, + recipient: utils.isMessageEmpty(message) + ? crossSwapQuotes.crossSwap.recipient + : getMultiCallHandlerAddress(destinationChainId), + inputToken: crossSwapQuotes.bridgeQuote.inputToken.address, + outputToken: crossSwapQuotes.bridgeQuote.outputToken.address, + inputAmount: crossSwapQuotes.bridgeQuote.inputAmount, + outputAmount: crossSwapQuotes.bridgeQuote.outputAmount, + destinationChainId, + exclusiveRelayer: + crossSwapQuotes.bridgeQuote.suggestedFees.exclusiveRelayer, + quoteTimestamp: crossSwapQuotes.bridgeQuote.suggestedFees.timestamp, + fillDeadline: await getFillDeadline(spokePool), + exclusivityDeadline: + crossSwapQuotes.bridgeQuote.suggestedFees.exclusivityDeadline, + exclusivityParameter: + crossSwapQuotes.bridgeQuote.suggestedFees.exclusivityDeadline, + message, + }; + return deposit; +} + +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); +} + +export function getQuoteFetchStrategy( + chainId: number, + strategies: QuoteFetchStrategies +) { + return strategies[chainId] ?? strategies.default ?? defaultQuoteFetchStrategy; +} + +export function buildDestinationSwapCrossChainMessage({ + crossSwap, + destinationSwapQuote, + bridgeableOutputToken, + routerAddress, +}: { + crossSwap: CrossSwap; + bridgeableOutputToken: Token; + destinationSwapQuote: SwapQuote; + routerAddress: string; +}) { + const destinationSwapChainId = destinationSwapQuote.tokenOut.chainId; + const isIndicativeQuote = + destinationSwapQuote.swapTx.to === "0x0" && + destinationSwapQuote.swapTx.data === "0x0" && + destinationSwapQuote.swapTx.value === "0x0"; + + let transferActions: { + target: string; + callData: string; + value: string; + }[] = []; + + // 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. + if (crossSwap.isOutputNative) { + transferActions = [ + { + target: crossSwap.outputToken.address, + callData: encodeWethWithdrawCalldata(crossSwap.amount), + value: "0", + }, + { + target: crossSwap.recipient, + callData: "0x", + value: crossSwap.amount.toString(), + }, + ]; + } + // If output token is an ERC-20 token and amount type is EXACT_OUTPUT, we need + // to transfer the EXACT output amount to the recipient. The refundAddress / depositor + // will receive any leftovers. + else if (crossSwap.type === AMOUNT_TYPE.EXACT_OUTPUT) { + transferActions = [ + { + target: crossSwap.outputToken.address, + callData: encodeTransferCalldata(crossSwap.recipient, crossSwap.amount), + value: "0", + }, + { + target: getMultiCallHandlerAddress(destinationSwapChainId), + callData: encodeDrainCalldata( + crossSwap.outputToken.address, + crossSwap.refundAddress ?? crossSwap.depositor + ), + value: "0", + }, + ]; + } + // If output token is an ERC-20 token and amount type is MIN_OUTPUT, we need + // to transfer all realized output tokens to the recipient. + else if (crossSwap.type === AMOUNT_TYPE.MIN_OUTPUT) { + transferActions = [ + { + target: getMultiCallHandlerAddress(destinationSwapChainId), + callData: encodeDrainCalldata( + crossSwap.outputToken.address, + crossSwap.recipient + ), + value: "0", + }, + ]; + } + + const swapActions = isIndicativeQuote + ? [] + : [ + { + target: destinationSwapQuote.swapTx.to, + callData: destinationSwapQuote.swapTx.data, + value: destinationSwapQuote.swapTx.value, + }, + ]; + + return buildMulticallHandlerMessage({ + fallbackRecipient: getFallbackRecipient(crossSwap), + actions: [ + // approve bridgeable output token + { + target: bridgeableOutputToken.address, + callData: encodeApproveCalldata( + routerAddress, + destinationSwapQuote.maximumAmountIn + ), + value: "0", + }, + // swap bridgeable output token -> cross swap output token + ...swapActions, + // transfer output tokens to recipient + ...transferActions, + // drain remaining bridgeable output tokens from MultiCallHandler contract + { + target: getMultiCallHandlerAddress(destinationSwapChainId), + callData: encodeDrainCalldata( + bridgeableOutputToken.address, + crossSwap.refundAddress ?? crossSwap.depositor + ), + value: "0", + }, + ], + }); +} + +export 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/_permit.ts b/api/_permit.ts index 90e7211ad..f03f3f799 100644 --- a/api/_permit.ts +++ b/api/_permit.ts @@ -44,9 +44,12 @@ export async function getPermitTypedData(params: { : domainSeparatorResult.status === "rejected" ? domainSeparatorResult.reason : new Error("Unknown error"); - throw new Error(`Contract ${params.tokenAddress} does not support permit`, { - cause: error, - }); + throw new Error( + `ERC-20 contract ${params.tokenAddress} does not support permit`, + { + cause: error, + } + ); } const name = nameResult.value; diff --git a/api/_spoke-pool-periphery.ts b/api/_spoke-pool-periphery.ts index 6e568682b..a399f75ca 100644 --- a/api/_spoke-pool-periphery.ts +++ b/api/_spoke-pool-periphery.ts @@ -1,26 +1,122 @@ -import { SpokePoolV3Periphery__factory } from "./_typechain/factories/SpokePoolV3Periphery"; +import { BigNumber } from "ethers"; +import { extractDepositDataStruct } from "./_dexes/utils"; +import { SpokePoolPeripheryProxy__factory } from "./_typechain/factories/SpokePoolPeripheryProxy__factory"; +import { SpokePoolV3Periphery__factory } from "./_typechain/factories/SpokePoolV3Periphery__factory"; import { ENABLED_ROUTES, getProvider } from "./_utils"; -export class UnknownPeripheryForDexOnChain extends Error { - constructor(chainId: number, dex: string) { - super(`Unknown SpokePoolPeriphery for DEX ${dex} on chain ${chainId}`); +const sharedEIP712Types = { + EIP712Domain: [ + { + name: "name", + type: "string", + }, + { + name: "version", + type: "string", + }, + { + name: "chainId", + type: "uint256", + }, + { + name: "verifyingContract", + type: "address", + }, + ], + Fees: [ + { + name: "amount", + type: "uint256", + }, + { + name: "recipient", + type: "address", + }, + ], + BaseDepositData: [ + { + name: "inputToken", + type: "address", + }, + { + name: "outputToken", + type: "address", + }, + { + name: "outputAmount", + type: "uint256", + }, + { + name: "depositor", + type: "address", + }, + { + name: "recipient", + type: "address", + }, + { + name: "destinationChainId", + type: "uint256", + }, + { + name: "exclusiveRelayer", + type: "address", + }, + { + name: "quoteTimestamp", + type: "uint32", + }, + { + name: "fillDeadline", + type: "uint32", + }, + { + name: "exclusivityParameter", + type: "uint32", + }, + { + name: "message", + type: "bytes", + }, + ], +}; + +export class UnknownPeripheryOnChain extends Error { + constructor(chainId: number) { + super(`Unknown 'SpokePoolPeriphery' on chain ${chainId}`); } } -export const spokePoolPeripheryDexes = Object.keys( - ENABLED_ROUTES.spokePoolPeripheryAddresses -); +export class UnknownPeripheryProxyOnChain extends Error { + constructor(chainId: number) { + super(`Unknown 'SpokePoolPeripheryProxy' on chain ${chainId}`); + } +} -export function getSpokePoolPeripheryAddress(dex: string, chainId: number) { - if (!_isDexSupported(dex)) { - throw new UnknownPeripheryForDexOnChain(chainId, dex); +export enum TransferType { + Approval = 0, + Transfer = 1, + Permit2Approval = 2, +} + +export function getSpokePoolPeripheryAddress(chainId: number) { + const address = + ENABLED_ROUTES.spokePoolPeripheryAddresses[ + chainId as keyof typeof ENABLED_ROUTES.spokePoolPeripheryAddresses + ]; + if (!address) { + throw new UnknownPeripheryOnChain(chainId); } + return address; +} - const address = ( - ENABLED_ROUTES.spokePoolPeripheryAddresses[dex] as Record - )?.[chainId]; +export function getSpokePoolPeripheryProxyAddress(chainId: number) { + const address = + ENABLED_ROUTES.spokePoolPeripheryProxyAddresses[ + chainId as keyof typeof ENABLED_ROUTES.spokePoolPeripheryProxyAddresses + ]; if (!address) { - throw new UnknownPeripheryForDexOnChain(chainId, dex); + throw new UnknownPeripheryProxyOnChain(chainId); } return address; } @@ -29,8 +125,138 @@ export function getSpokePoolPeriphery(address: string, chainId: number) { return SpokePoolV3Periphery__factory.connect(address, getProvider(chainId)); } -function _isDexSupported( - dex: string -): dex is keyof typeof ENABLED_ROUTES.spokePoolPeripheryAddresses { - return spokePoolPeripheryDexes.includes(dex); +export function getSpokePoolPeripheryProxy(address: string, chainId: number) { + return SpokePoolPeripheryProxy__factory.connect( + address, + getProvider(chainId) + ); +} + +export async function getDepositTypedData(params: { + depositData: { + submissionFees: { + amount: BigNumber; + recipient: string; + }; + baseDepositData: Awaited>; + inputAmount: BigNumber; + }; + chainId: number; +}) { + const spokePoolPeriphery = getSpokePoolPeriphery( + getSpokePoolPeripheryAddress(params.chainId), + params.chainId + ); + const [domainSeparatorHash, eip712Domain] = await Promise.all([ + spokePoolPeriphery.domainSeparator(), + spokePoolPeriphery.eip712Domain(), + ]); + + return { + domainSeparator: domainSeparatorHash, + eip712: { + types: { + ...sharedEIP712Types, + DepositData: [ + { + name: "submissionFees", + type: "Fees", + }, + { + name: "baseDepositData", + type: "BaseDepositData", + }, + { + name: "inputAmount", + type: "uint256", + }, + ], + }, + primaryType: "DepositData", + domain: { + name: eip712Domain.name, + version: eip712Domain.version, + chainId: params.chainId, + verifyingContract: eip712Domain.verifyingContract, + }, + message: params.depositData, + }, + }; +} + +export async function getSwapAndDepositTypedData(params: { + swapAndDepositData: { + submissionFees: { + amount: BigNumber; + recipient: string; + }; + depositData: Awaited>; + swapToken: string; + exchange: string; + transferType: TransferType; + swapTokenAmount: BigNumber; + minExpectedInputTokenAmount: BigNumber; + routerCalldata: string; + }; + chainId: number; +}) { + const spokePoolPeriphery = getSpokePoolPeriphery( + getSpokePoolPeripheryAddress(params.chainId), + params.chainId + ); + const [domainSeparatorHash, eip712Domain] = await Promise.all([ + spokePoolPeriphery.domainSeparator(), + spokePoolPeriphery.eip712Domain(), + ]); + + return { + domainSeparator: domainSeparatorHash, + eip712: { + types: { + ...sharedEIP712Types, + SwapAndDepositData: [ + { + name: "submissionFees", + type: "Fees", + }, + { + name: "depositData", + type: "BaseDepositData", + }, + { + name: "swapToken", + type: "address", + }, + { + name: "exchange", + type: "address", + }, + { + name: "transferType", + type: "uint8", + }, + { + name: "swapTokenAmount", + type: "uint256", + }, + { + name: "minExpectedInputTokenAmount", + type: "uint256", + }, + { + name: "routerCalldata", + type: "bytes", + }, + ], + }, + primaryType: "SwapAndDepositData", + domain: { + name: eip712Domain.name, + version: eip712Domain.version, + chainId: params.chainId, + verifyingContract: eip712Domain.verifyingContract, + }, + message: params.swapAndDepositData, + }, + }; } diff --git a/api/_swap-and-bridge.ts b/api/_swap-and-bridge.ts new file mode 100644 index 000000000..f57ee5eb5 --- /dev/null +++ b/api/_swap-and-bridge.ts @@ -0,0 +1,108 @@ +import { + SwapAndBridge__factory, + UniversalSwapAndBridge__factory, +} from "@across-protocol/contracts"; + +import { ENABLED_ROUTES, getProvider } from "./_utils"; + +type SwapAndBridgeType = "SwapAndBridge" | "UniversalSwapAndBridge"; + +export class UnsupportedDex extends Error { + constructor(dex: string, type: SwapAndBridgeType) { + super(`DEX/Aggregator '${dex}' not supported for '${type}'`); + } +} + +export class UnsupportedDexOnChain extends Error { + constructor(chainId: number, dex: string, type: SwapAndBridgeType) { + super( + `DEX/Aggregator '${dex}' not supported on chain ${chainId} for '${type}'` + ); + } +} + +export class NoSwapRouteError extends Error { + constructor(args: { + dex: string; + tokenInSymbol: string; + tokenOutSymbol: string; + chainId: number; + swapType: string; + }) { + super( + `No ${args.dex} swap route found for '${args.swapType}' ${args.tokenInSymbol} to ${args.tokenOutSymbol} on chain ${args.chainId}` + ); + } +} + +export const swapAndBridgeDexes = Object.keys( + ENABLED_ROUTES.swapAndBridgeAddresses +); + +export const universalSwapAndBridgeDexes = Object.keys( + ENABLED_ROUTES.universalSwapAndBridgeAddresses +); + +export function getSwapAndBridgeAddress(dex: string, chainId: number) { + if (!_isDexSupportedForSwapAndBridge(dex)) { + throw new UnsupportedDex(dex, "SwapAndBridge"); + } + + const address = ( + ENABLED_ROUTES.swapAndBridgeAddresses[dex] as Record + )?.[chainId]; + if (!address) { + throw new UnsupportedDexOnChain(chainId, dex, "SwapAndBridge"); + } + return address; +} + +export function getUniversalSwapAndBridgeAddress(dex: string, chainId: number) { + if (!_isDexSupportedForUniversalSwapAndBridge(dex)) { + throw new UnsupportedDex(dex, "UniversalSwapAndBridge"); + } + + const address = ( + ENABLED_ROUTES.universalSwapAndBridgeAddresses[dex] as Record< + string, + string + > + )?.[chainId]; + if (!address) { + throw new UnsupportedDexOnChain(chainId, dex, "UniversalSwapAndBridge"); + } + return address; +} + +export function getSwapAndBridge(dex: string, chainId: number) { + const swapAndBridgeAddress = getSwapAndBridgeAddress(dex, chainId); + + return SwapAndBridge__factory.connect( + swapAndBridgeAddress, + getProvider(chainId) + ); +} + +export function getUniversalSwapAndBridge(dex: string, chainId: number) { + const universalSwapAndBridgeAddress = getUniversalSwapAndBridgeAddress( + dex, + chainId + ); + + return UniversalSwapAndBridge__factory.connect( + universalSwapAndBridgeAddress, + getProvider(chainId) + ); +} + +function _isDexSupportedForSwapAndBridge( + dex: string +): dex is keyof typeof ENABLED_ROUTES.swapAndBridgeAddresses { + return swapAndBridgeDexes.includes(dex); +} + +function _isDexSupportedForUniversalSwapAndBridge( + dex: string +): dex is keyof typeof ENABLED_ROUTES.universalSwapAndBridgeAddresses { + return universalSwapAndBridgeDexes.includes(dex); +} diff --git a/api/_typechain/SpokePoolPeripheryProxy.ts b/api/_typechain/SpokePoolPeripheryProxy.ts new file mode 100644 index 000000000..cff3b940f --- /dev/null +++ b/api/_typechain/SpokePoolPeripheryProxy.ts @@ -0,0 +1,268 @@ +/* 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 } from "@ethersproject/abi"; +import type { Listener, Provider } from "@ethersproject/providers"; +import type { + TypedEventFilter, + TypedEvent, + TypedListener, + OnEvent, +} from "@across-protocol/contracts/dist/typechain/common"; + +export namespace SpokePoolV3PeripheryInterface { + export type FeesStruct = { amount: BigNumberish; recipient: string }; + + export type FeesStructOutput = [BigNumber, string] & { + amount: BigNumber; + recipient: string; + }; + + export type BaseDepositDataStruct = { + inputToken: string; + outputToken: string; + outputAmount: BigNumberish; + depositor: string; + recipient: string; + destinationChainId: BigNumberish; + exclusiveRelayer: string; + quoteTimestamp: BigNumberish; + fillDeadline: BigNumberish; + exclusivityParameter: BigNumberish; + message: BytesLike; + }; + + export type BaseDepositDataStructOutput = [ + string, + string, + BigNumber, + string, + string, + BigNumber, + string, + number, + number, + number, + string, + ] & { + inputToken: string; + outputToken: string; + outputAmount: BigNumber; + depositor: string; + recipient: string; + destinationChainId: BigNumber; + exclusiveRelayer: string; + quoteTimestamp: number; + fillDeadline: number; + exclusivityParameter: number; + message: string; + }; + + export type SwapAndDepositDataStruct = { + submissionFees: SpokePoolV3PeripheryInterface.FeesStruct; + depositData: SpokePoolV3PeripheryInterface.BaseDepositDataStruct; + swapToken: string; + exchange: string; + transferType: BigNumberish; + swapTokenAmount: BigNumberish; + minExpectedInputTokenAmount: BigNumberish; + routerCalldata: BytesLike; + }; + + export type SwapAndDepositDataStructOutput = [ + SpokePoolV3PeripheryInterface.FeesStructOutput, + SpokePoolV3PeripheryInterface.BaseDepositDataStructOutput, + string, + string, + number, + BigNumber, + BigNumber, + string, + ] & { + submissionFees: SpokePoolV3PeripheryInterface.FeesStructOutput; + depositData: SpokePoolV3PeripheryInterface.BaseDepositDataStructOutput; + swapToken: string; + exchange: string; + transferType: number; + swapTokenAmount: BigNumber; + minExpectedInputTokenAmount: BigNumber; + routerCalldata: string; + }; +} + +export interface SpokePoolPeripheryProxyInterface extends utils.Interface { + functions: { + "SPOKE_POOL_PERIPHERY()": FunctionFragment; + "initialize(address)": FunctionFragment; + "multicall(bytes[])": FunctionFragment; + "swapAndBridge(((uint256,address),(address,address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes),address,address,uint8,uint256,uint256,bytes))": FunctionFragment; + }; + + getFunction( + nameOrSignatureOrTopic: + | "SPOKE_POOL_PERIPHERY" + | "initialize" + | "multicall" + | "swapAndBridge" + ): FunctionFragment; + + encodeFunctionData( + functionFragment: "SPOKE_POOL_PERIPHERY", + values?: undefined + ): string; + encodeFunctionData(functionFragment: "initialize", values: [string]): string; + encodeFunctionData( + functionFragment: "multicall", + values: [BytesLike[]] + ): string; + encodeFunctionData( + functionFragment: "swapAndBridge", + values: [SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct] + ): string; + + decodeFunctionResult( + functionFragment: "SPOKE_POOL_PERIPHERY", + data: BytesLike + ): Result; + decodeFunctionResult(functionFragment: "initialize", data: BytesLike): Result; + decodeFunctionResult(functionFragment: "multicall", data: BytesLike): Result; + decodeFunctionResult( + functionFragment: "swapAndBridge", + data: BytesLike + ): Result; + + events: {}; +} + +export interface SpokePoolPeripheryProxy extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + interface: SpokePoolPeripheryProxyInterface; + + 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: { + SPOKE_POOL_PERIPHERY(overrides?: CallOverrides): Promise<[string]>; + + initialize( + _spokePoolPeriphery: string, + overrides?: Overrides & { from?: string } + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridge( + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + overrides?: Overrides & { from?: string } + ): Promise; + }; + + SPOKE_POOL_PERIPHERY(overrides?: CallOverrides): Promise; + + initialize( + _spokePoolPeriphery: string, + overrides?: Overrides & { from?: string } + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridge( + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + overrides?: Overrides & { from?: string } + ): Promise; + + callStatic: { + SPOKE_POOL_PERIPHERY(overrides?: CallOverrides): Promise; + + initialize( + _spokePoolPeriphery: string, + overrides?: CallOverrides + ): Promise; + + multicall(data: BytesLike[], overrides?: CallOverrides): Promise; + + swapAndBridge( + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + overrides?: CallOverrides + ): Promise; + }; + + filters: {}; + + estimateGas: { + SPOKE_POOL_PERIPHERY(overrides?: CallOverrides): Promise; + + initialize( + _spokePoolPeriphery: string, + overrides?: Overrides & { from?: string } + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridge( + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + overrides?: Overrides & { from?: string } + ): Promise; + }; + + populateTransaction: { + SPOKE_POOL_PERIPHERY( + overrides?: CallOverrides + ): Promise; + + initialize( + _spokePoolPeriphery: string, + overrides?: Overrides & { from?: string } + ): Promise; + + multicall( + data: BytesLike[], + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridge( + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + overrides?: Overrides & { from?: string } + ): Promise; + }; +} diff --git a/api/_typechain/SpokePoolV3Periphery.ts b/api/_typechain/SpokePoolV3Periphery.ts index ed66cf043..af3321c12 100644 --- a/api/_typechain/SpokePoolV3Periphery.ts +++ b/api/_typechain/SpokePoolV3Periphery.ts @@ -27,13 +27,21 @@ import type { OnEvent, } from "@across-protocol/contracts/dist/typechain/common"; -export declare namespace SpokePoolV3Periphery { - export type DepositDataStruct = { +export declare namespace SpokePoolV3PeripheryInterface { + export type FeesStruct = { amount: BigNumberish; recipient: string }; + + export type FeesStructOutput = [BigNumber, string] & { + amount: BigNumber; + recipient: string; + }; + + export type BaseDepositDataStruct = { + inputToken: string; outputToken: string; outputAmount: BigNumberish; depositor: string; recipient: string; - destinationChainid: BigNumberish; + destinationChainId: BigNumberish; exclusiveRelayer: string; quoteTimestamp: BigNumberish; fillDeadline: BigNumberish; @@ -41,7 +49,8 @@ export declare namespace SpokePoolV3Periphery { message: BytesLike; }; - export type DepositDataStructOutput = [ + export type BaseDepositDataStructOutput = [ + string, string, BigNumber, string, @@ -53,53 +62,134 @@ export declare namespace SpokePoolV3Periphery { number, string, ] & { + inputToken: string; outputToken: string; outputAmount: BigNumber; depositor: string; recipient: string; - destinationChainid: BigNumber; + destinationChainId: BigNumber; exclusiveRelayer: string; quoteTimestamp: number; fillDeadline: number; exclusivityParameter: number; message: string; }; + + export type DepositDataStruct = { + submissionFees: SpokePoolV3PeripheryInterface.FeesStruct; + baseDepositData: SpokePoolV3PeripheryInterface.BaseDepositDataStruct; + inputAmount: BigNumberish; + }; + + export type DepositDataStructOutput = [ + SpokePoolV3PeripheryInterface.FeesStructOutput, + SpokePoolV3PeripheryInterface.BaseDepositDataStructOutput, + BigNumber, + ] & { + submissionFees: SpokePoolV3PeripheryInterface.FeesStructOutput; + baseDepositData: SpokePoolV3PeripheryInterface.BaseDepositDataStructOutput; + inputAmount: BigNumber; + }; + + export type SwapAndDepositDataStruct = { + submissionFees: SpokePoolV3PeripheryInterface.FeesStruct; + depositData: SpokePoolV3PeripheryInterface.BaseDepositDataStruct; + swapToken: string; + exchange: string; + transferType: BigNumberish; + swapTokenAmount: BigNumberish; + minExpectedInputTokenAmount: BigNumberish; + routerCalldata: BytesLike; + }; + + export type SwapAndDepositDataStructOutput = [ + SpokePoolV3PeripheryInterface.FeesStructOutput, + SpokePoolV3PeripheryInterface.BaseDepositDataStructOutput, + string, + string, + number, + BigNumber, + BigNumber, + string, + ] & { + submissionFees: SpokePoolV3PeripheryInterface.FeesStructOutput; + depositData: SpokePoolV3PeripheryInterface.BaseDepositDataStructOutput; + swapToken: string; + exchange: string; + transferType: number; + swapTokenAmount: BigNumber; + minExpectedInputTokenAmount: BigNumber; + routerCalldata: string; + }; +} + +export declare namespace IPermit2 { + export type TokenPermissionsStruct = { token: string; amount: BigNumberish }; + + export type TokenPermissionsStructOutput = [string, BigNumber] & { + token: string; + amount: BigNumber; + }; + + export type PermitTransferFromStruct = { + permitted: IPermit2.TokenPermissionsStruct; + nonce: BigNumberish; + deadline: BigNumberish; + }; + + export type PermitTransferFromStructOutput = [ + IPermit2.TokenPermissionsStructOutput, + BigNumber, + BigNumber, + ] & { + permitted: IPermit2.TokenPermissionsStructOutput; + nonce: BigNumber; + deadline: BigNumber; + }; } export interface SpokePoolV3PeripheryInterface extends utils.Interface { functions: { - "allowedSelectors(bytes4)": FunctionFragment; "deposit(address,address,uint256,uint256,uint256,address,uint32,uint32,uint32,bytes)": 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; - "exchange()": FunctionFragment; - "initialize(address,address,address)": FunctionFragment; + "depositWithAuthorization(address,((uint256,address),(address,address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes),uint256),uint256,uint256,bytes32,bytes,bytes)": FunctionFragment; + "depositWithPermit(address,((uint256,address),(address,address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes),uint256),uint256,bytes,bytes)": FunctionFragment; + "depositWithPermit2(address,((uint256,address),(address,address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes),uint256),((address,uint256),uint256,uint256),bytes)": FunctionFragment; + "domainSeparator()": FunctionFragment; + "eip712Domain()": FunctionFragment; + "initialize(address,address,address,address)": FunctionFragment; + "isValidSignature(bytes32,bytes)": FunctionFragment; "multicall(bytes[])": FunctionFragment; + "permit2()": FunctionFragment; + "proxy()": FunctionFragment; "spokePool()": 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; + "swapAndBridge(((uint256,address),(address,address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes),address,address,uint8,uint256,uint256,bytes))": FunctionFragment; + "swapAndBridgeWithAuthorization(address,((uint256,address),(address,address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes),address,address,uint8,uint256,uint256,bytes),uint256,uint256,bytes32,bytes,bytes)": FunctionFragment; + "swapAndBridgeWithPermit(address,((uint256,address),(address,address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes),address,address,uint8,uint256,uint256,bytes),uint256,bytes,bytes)": FunctionFragment; + "swapAndBridgeWithPermit2(address,((uint256,address),(address,address,uint256,address,address,uint256,address,uint32,uint32,uint32,bytes),address,address,uint8,uint256,uint256,bytes),((address,uint256),uint256,uint256),bytes)": FunctionFragment; + "wrappedNativeToken()": FunctionFragment; }; getFunction( nameOrSignatureOrTopic: - | "allowedSelectors" | "deposit" | "depositWithAuthorization" | "depositWithPermit" - | "exchange" + | "depositWithPermit2" + | "domainSeparator" + | "eip712Domain" | "initialize" + | "isValidSignature" | "multicall" + | "permit2" + | "proxy" | "spokePool" | "swapAndBridge" | "swapAndBridgeWithAuthorization" | "swapAndBridgeWithPermit" + | "swapAndBridgeWithPermit2" + | "wrappedNativeToken" ): FunctionFragment; - encodeFunctionData( - functionFragment: "allowedSelectors", - values: [BytesLike] - ): string; encodeFunctionData( functionFragment: "deposit", values: [ @@ -119,12 +209,10 @@ export interface SpokePoolV3PeripheryInterface extends utils.Interface { functionFragment: "depositWithAuthorization", values: [ string, - BigNumberish, - SpokePoolV3Periphery.DepositDataStruct, + SpokePoolV3PeripheryInterface.DepositDataStruct, BigNumberish, BigNumberish, BytesLike, - BigNumberish, BytesLike, BytesLike, ] @@ -133,48 +221,56 @@ export interface SpokePoolV3PeripheryInterface extends utils.Interface { functionFragment: "depositWithPermit", values: [ string, + SpokePoolV3PeripheryInterface.DepositDataStruct, BigNumberish, - SpokePoolV3Periphery.DepositDataStruct, - BigNumberish, - BigNumberish, BytesLike, BytesLike, ] ): string; - encodeFunctionData(functionFragment: "exchange", values?: undefined): string; + encodeFunctionData( + functionFragment: "depositWithPermit2", + values: [ + string, + SpokePoolV3PeripheryInterface.DepositDataStruct, + IPermit2.PermitTransferFromStruct, + BytesLike, + ] + ): string; + encodeFunctionData( + functionFragment: "domainSeparator", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "eip712Domain", + values?: undefined + ): string; encodeFunctionData( functionFragment: "initialize", - values: [string, string, string] + values: [string, string, string, string] + ): string; + encodeFunctionData( + functionFragment: "isValidSignature", + values: [BytesLike, BytesLike] ): string; encodeFunctionData( functionFragment: "multicall", values: [BytesLike[]] ): string; + encodeFunctionData(functionFragment: "permit2", values?: undefined): string; + encodeFunctionData(functionFragment: "proxy", values?: undefined): string; encodeFunctionData(functionFragment: "spokePool", values?: undefined): string; encodeFunctionData( functionFragment: "swapAndBridge", - values: [ - string, - string, - BytesLike, - BigNumberish, - BigNumberish, - SpokePoolV3Periphery.DepositDataStruct, - ] + values: [SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct] ): string; encodeFunctionData( functionFragment: "swapAndBridgeWithAuthorization", values: [ string, - string, - BytesLike, - BigNumberish, - BigNumberish, - SpokePoolV3Periphery.DepositDataStruct, + SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, BigNumberish, BigNumberish, BytesLike, - BigNumberish, BytesLike, BytesLike, ] @@ -183,22 +279,26 @@ export interface SpokePoolV3PeripheryInterface extends utils.Interface { functionFragment: "swapAndBridgeWithPermit", values: [ string, - string, - BytesLike, - BigNumberish, - BigNumberish, - SpokePoolV3Periphery.DepositDataStruct, - BigNumberish, + SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, BigNumberish, BytesLike, BytesLike, ] ): string; + encodeFunctionData( + functionFragment: "swapAndBridgeWithPermit2", + values: [ + string, + SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + IPermit2.PermitTransferFromStruct, + BytesLike, + ] + ): string; + encodeFunctionData( + functionFragment: "wrappedNativeToken", + values?: undefined + ): string; - decodeFunctionResult( - functionFragment: "allowedSelectors", - data: BytesLike - ): Result; decodeFunctionResult(functionFragment: "deposit", data: BytesLike): Result; decodeFunctionResult( functionFragment: "depositWithAuthorization", @@ -208,9 +308,26 @@ export interface SpokePoolV3PeripheryInterface extends utils.Interface { functionFragment: "depositWithPermit", data: BytesLike ): Result; - decodeFunctionResult(functionFragment: "exchange", data: BytesLike): Result; + decodeFunctionResult( + functionFragment: "depositWithPermit2", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "domainSeparator", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "eip712Domain", + data: BytesLike + ): Result; decodeFunctionResult(functionFragment: "initialize", data: BytesLike): Result; + decodeFunctionResult( + functionFragment: "isValidSignature", + data: BytesLike + ): Result; decodeFunctionResult(functionFragment: "multicall", data: BytesLike): Result; + decodeFunctionResult(functionFragment: "permit2", data: BytesLike): Result; + decodeFunctionResult(functionFragment: "proxy", data: BytesLike): Result; decodeFunctionResult(functionFragment: "spokePool", data: BytesLike): Result; decodeFunctionResult( functionFragment: "swapAndBridge", @@ -224,16 +341,36 @@ export interface SpokePoolV3PeripheryInterface extends utils.Interface { functionFragment: "swapAndBridgeWithPermit", data: BytesLike ): Result; + decodeFunctionResult( + functionFragment: "swapAndBridgeWithPermit2", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "wrappedNativeToken", + data: BytesLike + ): Result; events: { - "SwapBeforeBridge(address,address,address,uint256,uint256,address,uint256)": EventFragment; + "EIP712DomainChanged()": EventFragment; + "SwapBeforeBridge(address,bytes,address,address,uint256,uint256,address,uint256)": EventFragment; }; + getEvent(nameOrSignatureOrTopic: "EIP712DomainChanged"): EventFragment; getEvent(nameOrSignatureOrTopic: "SwapBeforeBridge"): EventFragment; } +export interface EIP712DomainChangedEventObject {} +export type EIP712DomainChangedEvent = TypedEvent< + [], + EIP712DomainChangedEventObject +>; + +export type EIP712DomainChangedEventFilter = + TypedEventFilter; + export interface SwapBeforeBridgeEventObject { exchange: string; + exchangeCalldata: string; swapToken: string; acrossInputToken: string; swapTokenAmount: BigNumber; @@ -242,7 +379,7 @@ export interface SwapBeforeBridgeEventObject { acrossOutputAmount: BigNumber; } export type SwapBeforeBridgeEvent = TypedEvent< - [string, string, string, BigNumber, BigNumber, string, BigNumber], + [string, string, string, string, BigNumber, BigNumber, string, BigNumber], SwapBeforeBridgeEventObject >; @@ -276,11 +413,6 @@ export interface SpokePoolV3Periphery extends BaseContract { removeListener: OnEvent; functions: { - allowedSelectors( - arg0: BytesLike, - overrides?: CallOverrides - ): Promise<[boolean]>; - deposit( recipient: string, inputToken: string, @@ -296,90 +428,107 @@ export interface SpokePoolV3Periphery extends BaseContract { ): Promise; depositWithAuthorization( - acrossInputToken: string, - acrossInputAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, validAfter: BigNumberish, validBefore: BigNumberish, nonce: BytesLike, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + receiveWithAuthSignature: BytesLike, + depositDataSignature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; depositWithPermit( - acrossInputToken: string, - acrossInputAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, deadline: BigNumberish, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + permitSignature: BytesLike, + depositDataSignature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + depositWithPermit2( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + permit: IPermit2.PermitTransferFromStruct, + signature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; - exchange(overrides?: CallOverrides): Promise<[string]>; + domainSeparator(overrides?: CallOverrides): Promise<[string]>; + + eip712Domain(overrides?: CallOverrides): Promise< + [string, string, string, BigNumber, string, string, BigNumber[]] & { + fields: string; + name: string; + version: string; + chainId: BigNumber; + verifyingContract: string; + salt: string; + extensions: BigNumber[]; + } + >; initialize( _spokePool: string, _wrappedNativeToken: string, - _exchange: string, + _proxy: string, + _permit2: string, overrides?: Overrides & { from?: string } ): Promise; + isValidSignature( + arg0: BytesLike, + arg1: BytesLike, + overrides?: CallOverrides + ): Promise<[string] & { magicBytes: string }>; + multicall( data: BytesLike[], overrides?: Overrides & { from?: string } ): Promise; + permit2(overrides?: CallOverrides): Promise<[string]>; + + proxy(overrides?: CallOverrides): Promise<[string]>; + spokePool(overrides?: CallOverrides): Promise<[string]>; swapAndBridge( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, overrides?: PayableOverrides & { from?: string } ): Promise; swapAndBridgeWithAuthorization( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, validAfter: BigNumberish, validBefore: BigNumberish, nonce: BytesLike, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + receiveWithAuthSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; swapAndBridgeWithPermit( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, deadline: BigNumberish, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + permitSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; - }; - allowedSelectors( - arg0: BytesLike, - overrides?: CallOverrides - ): Promise; + swapAndBridgeWithPermit2( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + permit: IPermit2.PermitTransferFromStruct, + signature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + wrappedNativeToken(overrides?: CallOverrides): Promise<[string]>; + }; deposit( recipient: string, @@ -396,91 +545,108 @@ export interface SpokePoolV3Periphery extends BaseContract { ): Promise; depositWithAuthorization( - acrossInputToken: string, - acrossInputAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, validAfter: BigNumberish, validBefore: BigNumberish, nonce: BytesLike, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + receiveWithAuthSignature: BytesLike, + depositDataSignature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; depositWithPermit( - acrossInputToken: string, - acrossInputAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, deadline: BigNumberish, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + permitSignature: BytesLike, + depositDataSignature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + depositWithPermit2( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + permit: IPermit2.PermitTransferFromStruct, + signature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; - exchange(overrides?: CallOverrides): Promise; + domainSeparator(overrides?: CallOverrides): Promise; + + eip712Domain(overrides?: CallOverrides): Promise< + [string, string, string, BigNumber, string, string, BigNumber[]] & { + fields: string; + name: string; + version: string; + chainId: BigNumber; + verifyingContract: string; + salt: string; + extensions: BigNumber[]; + } + >; initialize( _spokePool: string, _wrappedNativeToken: string, - _exchange: string, + _proxy: string, + _permit2: string, overrides?: Overrides & { from?: string } ): Promise; + isValidSignature( + arg0: BytesLike, + arg1: BytesLike, + overrides?: CallOverrides + ): Promise; + multicall( data: BytesLike[], overrides?: Overrides & { from?: string } ): Promise; + permit2(overrides?: CallOverrides): Promise; + + proxy(overrides?: CallOverrides): Promise; + spokePool(overrides?: CallOverrides): Promise; swapAndBridge( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, overrides?: PayableOverrides & { from?: string } ): Promise; swapAndBridgeWithAuthorization( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, validAfter: BigNumberish, validBefore: BigNumberish, nonce: BytesLike, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + receiveWithAuthSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; swapAndBridgeWithPermit( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, deadline: BigNumberish, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + permitSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; - callStatic: { - allowedSelectors( - arg0: BytesLike, - overrides?: CallOverrides - ): Promise; + swapAndBridgeWithPermit2( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + permit: IPermit2.PermitTransferFromStruct, + signature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + wrappedNativeToken(overrides?: CallOverrides): Promise; + callStatic: { deposit( recipient: string, inputToken: string, @@ -496,86 +662,112 @@ export interface SpokePoolV3Periphery extends BaseContract { ): Promise; depositWithAuthorization( - acrossInputToken: string, - acrossInputAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, validAfter: BigNumberish, validBefore: BigNumberish, nonce: BytesLike, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + receiveWithAuthSignature: BytesLike, + depositDataSignature: BytesLike, overrides?: CallOverrides ): Promise; depositWithPermit( - acrossInputToken: string, - acrossInputAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, deadline: BigNumberish, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + permitSignature: BytesLike, + depositDataSignature: BytesLike, + overrides?: CallOverrides + ): Promise; + + depositWithPermit2( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + permit: IPermit2.PermitTransferFromStruct, + signature: BytesLike, overrides?: CallOverrides ): Promise; - exchange(overrides?: CallOverrides): Promise; + domainSeparator(overrides?: CallOverrides): Promise; + + eip712Domain(overrides?: CallOverrides): Promise< + [string, string, string, BigNumber, string, string, BigNumber[]] & { + fields: string; + name: string; + version: string; + chainId: BigNumber; + verifyingContract: string; + salt: string; + extensions: BigNumber[]; + } + >; initialize( _spokePool: string, _wrappedNativeToken: string, - _exchange: string, + _proxy: string, + _permit2: string, overrides?: CallOverrides ): Promise; + isValidSignature( + arg0: BytesLike, + arg1: BytesLike, + overrides?: CallOverrides + ): Promise; + multicall(data: BytesLike[], overrides?: CallOverrides): Promise; + permit2(overrides?: CallOverrides): Promise; + + proxy(overrides?: CallOverrides): Promise; + spokePool(overrides?: CallOverrides): Promise; swapAndBridge( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, overrides?: CallOverrides ): Promise; swapAndBridgeWithAuthorization( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, validAfter: BigNumberish, validBefore: BigNumberish, nonce: BytesLike, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + receiveWithAuthSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, overrides?: CallOverrides ): Promise; swapAndBridgeWithPermit( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, deadline: BigNumberish, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + permitSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, + overrides?: CallOverrides + ): Promise; + + swapAndBridgeWithPermit2( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + permit: IPermit2.PermitTransferFromStruct, + signature: BytesLike, overrides?: CallOverrides ): Promise; + + wrappedNativeToken(overrides?: CallOverrides): Promise; }; filters: { - "SwapBeforeBridge(address,address,address,uint256,uint256,address,uint256)"( + "EIP712DomainChanged()"(): EIP712DomainChangedEventFilter; + EIP712DomainChanged(): EIP712DomainChangedEventFilter; + + "SwapBeforeBridge(address,bytes,address,address,uint256,uint256,address,uint256)"( exchange?: null, + exchangeCalldata?: null, swapToken?: string | null, acrossInputToken?: string | null, swapTokenAmount?: null, @@ -585,6 +777,7 @@ export interface SpokePoolV3Periphery extends BaseContract { ): SwapBeforeBridgeEventFilter; SwapBeforeBridge( exchange?: null, + exchangeCalldata?: null, swapToken?: string | null, acrossInputToken?: string | null, swapTokenAmount?: null, @@ -595,11 +788,6 @@ export interface SpokePoolV3Periphery extends BaseContract { }; estimateGas: { - allowedSelectors( - arg0: BytesLike, - overrides?: CallOverrides - ): Promise; - deposit( recipient: string, inputToken: string, @@ -615,92 +803,99 @@ export interface SpokePoolV3Periphery extends BaseContract { ): Promise; depositWithAuthorization( - acrossInputToken: string, - acrossInputAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, validAfter: BigNumberish, validBefore: BigNumberish, nonce: BytesLike, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + receiveWithAuthSignature: BytesLike, + depositDataSignature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; depositWithPermit( - acrossInputToken: string, - acrossInputAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, deadline: BigNumberish, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + permitSignature: BytesLike, + depositDataSignature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + depositWithPermit2( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + permit: IPermit2.PermitTransferFromStruct, + signature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; - exchange(overrides?: CallOverrides): Promise; + domainSeparator(overrides?: CallOverrides): Promise; + + eip712Domain(overrides?: CallOverrides): Promise; initialize( _spokePool: string, _wrappedNativeToken: string, - _exchange: string, + _proxy: string, + _permit2: string, overrides?: Overrides & { from?: string } ): Promise; + isValidSignature( + arg0: BytesLike, + arg1: BytesLike, + overrides?: CallOverrides + ): Promise; + multicall( data: BytesLike[], overrides?: Overrides & { from?: string } ): Promise; + permit2(overrides?: CallOverrides): Promise; + + proxy(overrides?: CallOverrides): Promise; + spokePool(overrides?: CallOverrides): Promise; swapAndBridge( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, overrides?: PayableOverrides & { from?: string } ): Promise; swapAndBridgeWithAuthorization( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, validAfter: BigNumberish, validBefore: BigNumberish, nonce: BytesLike, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + receiveWithAuthSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; swapAndBridgeWithPermit( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, deadline: BigNumberish, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + permitSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; + + swapAndBridgeWithPermit2( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + permit: IPermit2.PermitTransferFromStruct, + signature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + wrappedNativeToken(overrides?: CallOverrides): Promise; }; populateTransaction: { - allowedSelectors( - arg0: BytesLike, - overrides?: CallOverrides - ): Promise; - deposit( recipient: string, inputToken: string, @@ -716,83 +911,97 @@ export interface SpokePoolV3Periphery extends BaseContract { ): Promise; depositWithAuthorization( - acrossInputToken: string, - acrossInputAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, validAfter: BigNumberish, validBefore: BigNumberish, nonce: BytesLike, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + receiveWithAuthSignature: BytesLike, + depositDataSignature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; depositWithPermit( - acrossInputToken: string, - acrossInputAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, deadline: BigNumberish, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + permitSignature: BytesLike, + depositDataSignature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + depositWithPermit2( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + permit: IPermit2.PermitTransferFromStruct, + signature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; - exchange(overrides?: CallOverrides): Promise; + domainSeparator(overrides?: CallOverrides): Promise; + + eip712Domain(overrides?: CallOverrides): Promise; initialize( _spokePool: string, _wrappedNativeToken: string, - _exchange: string, + _proxy: string, + _permit2: string, overrides?: Overrides & { from?: string } ): Promise; + isValidSignature( + arg0: BytesLike, + arg1: BytesLike, + overrides?: CallOverrides + ): Promise; + multicall( data: BytesLike[], overrides?: Overrides & { from?: string } ): Promise; + permit2(overrides?: CallOverrides): Promise; + + proxy(overrides?: CallOverrides): Promise; + spokePool(overrides?: CallOverrides): Promise; swapAndBridge( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, overrides?: PayableOverrides & { from?: string } ): Promise; swapAndBridgeWithAuthorization( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, validAfter: BigNumberish, validBefore: BigNumberish, nonce: BytesLike, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + receiveWithAuthSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; swapAndBridgeWithPermit( - swapToken: string, - acrossInputToken: string, - routerCalldata: BytesLike, - swapTokenAmount: BigNumberish, - minExpectedInputTokenAmount: BigNumberish, - depositData: SpokePoolV3Periphery.DepositDataStruct, + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, deadline: BigNumberish, - v: BigNumberish, - r: BytesLike, - s: BytesLike, + permitSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, overrides?: Overrides & { from?: string } ): Promise; + + swapAndBridgeWithPermit2( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + permit: IPermit2.PermitTransferFromStruct, + signature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + wrappedNativeToken( + overrides?: CallOverrides + ): Promise; }; } diff --git a/api/_typechain/factories/SpokePoolPeripheryProxy__factory.ts b/api/_typechain/factories/SpokePoolPeripheryProxy__factory.ts new file mode 100644 index 000000000..863a89c2e --- /dev/null +++ b/api/_typechain/factories/SpokePoolPeripheryProxy__factory.ts @@ -0,0 +1,251 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ + +import { Signer, utils, Contract, ContractFactory, Overrides } from "ethers"; +import type { Provider, TransactionRequest } from "@ethersproject/providers"; +import type { + SpokePoolPeripheryProxy, + SpokePoolPeripheryProxyInterface, +} from "../SpokePoolPeripheryProxy"; + +const _abi = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "ContractInitialized", + type: "error", + }, + { + inputs: [], + name: "InvalidPeriphery", + type: "error", + }, + { + inputs: [], + name: "SPOKE_POOL_PERIPHERY", + outputs: [ + { + internalType: "contract SpokePoolV3Periphery", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "contract SpokePoolV3Periphery", + name: "_spokePoolPeriphery", + type: "address", + }, + ], + name: "initialize", + 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: [ + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.Fees", + name: "submissionFees", + type: "tuple", + }, + { + components: [ + { + internalType: "address", + name: "inputToken", + type: "address", + }, + { + 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: "exclusivityParameter", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: + "struct SpokePoolV3PeripheryInterface.BaseDepositData", + name: "depositData", + type: "tuple", + }, + { + internalType: "address", + name: "swapToken", + type: "address", + }, + { + internalType: "address", + name: "exchange", + type: "address", + }, + { + internalType: "enum SpokePoolV3PeripheryInterface.TransferType", + name: "transferType", + type: "uint8", + }, + { + internalType: "uint256", + name: "swapTokenAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "minExpectedInputTokenAmount", + type: "uint256", + }, + { + internalType: "bytes", + name: "routerCalldata", + type: "bytes", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.SwapAndDepositData", + name: "swapAndDepositData", + type: "tuple", + }, + ], + name: "swapAndBridge", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; + +const _bytecode = + "0x6080806040523461002157600160ff195f5416175f55610ee790816100268239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063894adf3014610054578063991fe5901461004f578063ac9650d81461004a5763c4d66de814610045575f80fd5b6103e1565b61033b565b610213565b34610205577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc602081360112610205576004359067ffffffffffffffff821161020557610120908236030112610205576100ac61085f565b6100d77fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b61013e6101026100e96064840161086b565b73ffffffffffffffffffffffffffffffffffffffff1690565b60c48301359061011482303384610c21565b5f546101389060101c73ffffffffffffffffffffffffffffffffffffffff166100e9565b90610c9d565b5f546101629060101c73ffffffffffffffffffffffffffffffffffffffff166100e9565b803b15610205575f6101a88192846040519485809481937f894adf3000000000000000000000000000000000000000000000000000000000835260040160048301610af5565b03925af18015610200576101e7575b6101e560017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f5416175f55565b005b806101f46101fa9261056c565b80610209565b806101b7565b610c16565b5f80fd5b5f91031261020557565b34610205575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261020557602073ffffffffffffffffffffffffffffffffffffffff5f5460101c16604051908152f35b5f5b8381106102775750505f910152565b8181015183820152602001610268565b602080820190808352835180925260408301928160408460051b8301019501935f915b8483106102ba5750505050505090565b909192939495848080837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc086600196030187527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8c5161032781518092818752878088019101610266565b0116010198019301930191949392906102aa565b346102055760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102055767ffffffffffffffff6004358181116102055736602382011215610205578060040135918211610205573660248360051b83010111610205576103bf9160246103b39201610778565b60405191829182610287565b0390f35b73ffffffffffffffffffffffffffffffffffffffff81160361020557565b346102055760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102055760043561041c816103c3565b61042461085f565b61044f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b5f5460ff8160081c16610515577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff16610100175f5573ffffffffffffffffffffffffffffffffffffffff81163b156104eb576101b7907fffffffffffffffffffff0000000000000000000000000000000000000000ffff75ffffffffffffffffffffffffffffffffffffffff00005f549260101b169116175f55565b60046040517fd07c0ecb000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9f4eefba000000000000000000000000000000000000000000000000000000008152fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b67ffffffffffffffff811161058057604052565b61053f565b6080810190811067ffffffffffffffff82111761058057604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761058057604052565b67ffffffffffffffff81116105805760051b60200190565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b91908110156106875760051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561020557019081359167ffffffffffffffff8311610205576020018236038113610205579190565b6105fa565b908092918237015f815290565b67ffffffffffffffff811161058057601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b3d156106fd573d906106e482610699565b916106f260405193846105a1565b82523d5f602084013e565b606090565b6020818303126102055780519067ffffffffffffffff8211610205570181601f8201121561020557805161073581610699565b9261074360405194856105a1565b81845260208284010111610205576107619160208085019101610266565b90565b80518210156106875760209160051b010190565b919091610784836105e2565b90604061079460405193846105a1565b8483527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06107c1866105e2565b015f5b81811061084e57505082945f5b8181106107df575050505050565b5f806107ec838588610627565b906107fb87518093819361068c565b0390305af46108086106d3565b901561082e579060019161081c8288610764565b526108278187610764565b50016107d1565b604481511061020557806004610205920151602480918301019101610702565b8060606020809388010152016107c4565b60ff5f54161561020557565b35610761816103c3565b3590610880826103c3565b565b90357ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffea182360301811215610205570190565b359063ffffffff8216820361020557565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18236030181121561020557016020813591019167ffffffffffffffff821161020557813603831361020557565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093818652868601375f8582860101520116010190565b6107619161016081610983829361096986610875565b73ffffffffffffffffffffffffffffffffffffffff169052565b6109af61099260208601610875565b73ffffffffffffffffffffffffffffffffffffffff166020830152565b604084013560408201526109e56109c860608601610875565b73ffffffffffffffffffffffffffffffffffffffff166060830152565b610a116109f460808601610875565b73ffffffffffffffffffffffffffffffffffffffff166080830152565b60a084013560a0820152610a47610a2a60c08601610875565b73ffffffffffffffffffffffffffffffffffffffff1660c0830152565b610a63610a5660e086016108b4565b63ffffffff1660e0830152565b610a80610100610a748187016108b4565b63ffffffff1690830152565b610a91610120610a748187016108b4565b610aa161014094858101906108c5565b9390948201520191610915565b3590600382101561020557565b906003821015610ac85752565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b9061076191602081528135602082015273ffffffffffffffffffffffffffffffffffffffff6020830135610b28816103c3565b166040820152610b3b6040830183610882565b610be6610b5661012092836060860152610140850190610953565b93610b83610b6660608301610875565b73ffffffffffffffffffffffffffffffffffffffff166080860152565b610baf610b9260808301610875565b73ffffffffffffffffffffffffffffffffffffffff1660a0860152565b610bc8610bbe60a08301610aae565b60c0860190610abb565b60c081013560e085015261010060e0820135818601528101906108c5565b9290917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082860301910152610915565b6040513d5f823e3d90fd5b9290604051927f23b872dd00000000000000000000000000000000000000000000000000000000602085015273ffffffffffffffffffffffffffffffffffffffff809216602485015216604483015260648201526064815260a081019181831067ffffffffffffffff8411176105805761088092604052610ddd565b91909160405191602083015f807f095ea7b3000000000000000000000000000000000000000000000000000000009384845273ffffffffffffffffffffffffffffffffffffffff908189166024890152604488015260448752610cff87610585565b85169286519082855af190610d126106d3565b82610d93575b5081610d88575b5015610d2c575b50505050565b604051602081019190915273ffffffffffffffffffffffffffffffffffffffff9390931660248401525f6044808501919091528352610d7f92610d7a90610d746064826105a1565b82610ddd565b610ddd565b5f808080610d26565b90503b15155f610d1f565b80519192508115918215610dab575b5050905f610d18565b610dbe9250602080918301019101610dc5565b5f80610da2565b90816020910312610205575180151581036102055790565b73ffffffffffffffffffffffffffffffffffffffff166040516040810181811067ffffffffffffffff82111761058057610e58937f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656460205f948594604052818152015260208151910182855af1610e526106d3565b91610e88565b8051908115918215610e6e575b50501561020557565b610e819250602080918301019101610dc5565b5f80610e65565b9015610ea257815115610e99575090565b3b156102055790565b50805190811561020557602001fdfea26469706673582212207b2f1c8c1d5063a21ba98aa274966178205f188d68c4886cd240583330d63c3d64736f6c63430008170033"; + +type SpokePoolPeripheryProxyConstructorParams = + | [signer?: Signer] + | ConstructorParameters; + +const isSuperArgs = ( + xs: SpokePoolPeripheryProxyConstructorParams +): xs is ConstructorParameters => xs.length > 1; + +export class SpokePoolPeripheryProxy__factory extends ContractFactory { + constructor(...args: SpokePoolPeripheryProxyConstructorParams) { + if (isSuperArgs(args)) { + super(...args); + } else { + super(_abi, _bytecode, args[0]); + } + } + + override deploy( + overrides?: Overrides & { from?: string } + ): Promise { + return super.deploy(overrides || {}) as Promise; + } + override getDeployTransaction( + overrides?: Overrides & { from?: string } + ): TransactionRequest { + return super.getDeployTransaction(overrides || {}); + } + override attach(address: string): SpokePoolPeripheryProxy { + return super.attach(address) as SpokePoolPeripheryProxy; + } + override connect(signer: Signer): SpokePoolPeripheryProxy__factory { + return super.connect(signer) as SpokePoolPeripheryProxy__factory; + } + + static readonly bytecode = _bytecode; + static readonly abi = _abi; + static createInterface(): SpokePoolPeripheryProxyInterface { + return new utils.Interface(_abi) as SpokePoolPeripheryProxyInterface; + } + static connect( + address: string, + signerOrProvider: Signer | Provider + ): SpokePoolPeripheryProxy { + return new Contract( + address, + _abi, + signerOrProvider + ) as SpokePoolPeripheryProxy; + } +} diff --git a/api/_typechain/factories/SpokePoolV3Periphery.ts b/api/_typechain/factories/SpokePoolV3Periphery.ts deleted file mode 100644 index 4b85fa045..000000000 --- a/api/_typechain/factories/SpokePoolV3Periphery.ts +++ /dev/null @@ -1,838 +0,0 @@ -/* 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 { - SpokePoolV3Periphery, - SpokePoolV3PeripheryInterface, -} from "../SpokePoolV3Periphery"; - -const _abi = [ - { - inputs: [ - { - internalType: "bytes4[]", - name: "_allowedSelectors", - type: "bytes4[]", - }, - ], - stateMutability: "nonpayable", - type: "constructor", - }, - { - inputs: [], - name: "ContractInitialized", - type: "error", - }, - { - inputs: [], - name: "InvalidFunctionSelector", - type: "error", - }, - { - inputs: [], - name: "InvalidMsgValue", - type: "error", - }, - { - inputs: [], - name: "InvalidSpokePool", - 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: [ - { - internalType: "bytes4", - name: "", - type: "bytes4", - }, - ], - name: "allowedSelectors", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "recipient", - type: "address", - }, - { - internalType: "address", - name: "inputToken", - type: "address", - }, - { - internalType: "uint256", - name: "inputAmount", - type: "uint256", - }, - { - internalType: "uint256", - name: "outputAmount", - type: "uint256", - }, - { - 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: "exclusivityParameter", - type: "uint32", - }, - { - internalType: "bytes", - name: "message", - type: "bytes", - }, - ], - name: "deposit", - outputs: [], - stateMutability: "payable", - 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: "exclusivityParameter", - type: "uint32", - }, - { - internalType: "bytes", - name: "message", - type: "bytes", - }, - ], - internalType: "struct SpokePoolV3Periphery.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: "exclusivityParameter", - type: "uint32", - }, - { - internalType: "bytes", - name: "message", - type: "bytes", - }, - ], - internalType: "struct SpokePoolV3Periphery.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: [], - name: "exchange", - outputs: [ - { - internalType: "address", - name: "", - type: "address", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "contract V3SpokePoolInterface", - name: "_spokePool", - type: "address", - }, - { - internalType: "contract WETH9Interface", - name: "_wrappedNativeToken", - type: "address", - }, - { - internalType: "address", - name: "_exchange", - type: "address", - }, - ], - name: "initialize", - 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: [], - name: "spokePool", - outputs: [ - { - internalType: "contract V3SpokePoolInterface", - name: "", - type: "address", - }, - ], - stateMutability: "view", - 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: "exclusivityParameter", - type: "uint32", - }, - { - internalType: "bytes", - name: "message", - type: "bytes", - }, - ], - internalType: "struct SpokePoolV3Periphery.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: "exclusivityParameter", - type: "uint32", - }, - { - internalType: "bytes", - name: "message", - type: "bytes", - }, - ], - internalType: "struct SpokePoolV3Periphery.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: "exclusivityParameter", - type: "uint32", - }, - { - internalType: "bytes", - name: "message", - type: "bytes", - }, - ], - internalType: "struct SpokePoolV3Periphery.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 = - "0x60406080604052346200011c5762001e1e803803806200001f8162000134565b928339810160209081838203126200011c5782516001600160401b03938482116200011c57019080601f830112156200011c57815193841162000120576005918460051b9084806200007381850162000134565b8098815201928201019283116200011c578401905b828210620000fa57505050600193849360ff19936001855f5416175f555f955b620000be575b604051611cc390816200015b8239f35b8151861015620000f45786809663ffffffff60e01b8382881b86010151165f52818352845f2082888254161790550195620000a8565b620000ae565b81516001600160e01b0319811681036200011c57815290840190840162000088565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b6040519190601f01601f191682016001600160401b03811183821017620001205760405256fe60806040526004361015610011575f80fd5b5f3560e01c8063038f12ea146100c4578063277deffe146100bf57806327e98fbd146100ba57806385f168eb146100b5578063ac9650d8146100b0578063afdac3d6146100ab578063bdf52ad3146100a6578063c0c53b8b146100a1578063c51e5eb91461009c578063d2f7265a146100975763fdf152d314610092575f80fd5b610b13565b610ac2565b6108a0565b61074d565b6106c0565b61066f565b6105dd565b610481565b61039b565b610224565b610175565b73ffffffffffffffffffffffffffffffffffffffff8116036100e757565b5f80fd5b600435906100f8826100c9565b565b602435906100f8826100c9565b9181601f840112156100e75782359167ffffffffffffffff83116100e757602083818601950101116100e757565b90816101409103126100e75790565b610124359060ff821682036100e757565b60e4359060ff821682036100e757565b6084359060ff821682036100e757565b346100e7576101807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e7576101ad6100eb565b6101b56100fa565b67ffffffffffffffff91906044358381116100e7576101d8903690600401610107565b9260a4359485116100e7576101f4610222953690600401610135565b936101fd610144565b9261016435956101443595610104359460e4359460c435946084359360643593610c95565b005b346100e7576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e757600435610260816100c9565b6102686100fa565b67ffffffffffffffff91906044358381116100e75761028b903690600401610107565b919060a4359485116100e7576102a8610222953690600401610135565b6102b0610155565b926101243595610104359560c435946084359360643593610dc0565b63ffffffff8116036100e757565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b67ffffffffffffffff811161031b57604052565b6102da565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761031b57604052565b67ffffffffffffffff811161031b57601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b6101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e7576004356103d2816100c9565b6024356103de816100c9565b60a4356103ea816100c9565b60c4356103f6816102cc565b60e43590610403826102cc565b6101043592610411846102cc565b610124359567ffffffffffffffff87116100e757366023880112156100e75786600401359561043f87610361565b9661044d6040519889610320565b808852366024828b0101116100e7576020815f9260246102229c01838c013789010152608435916064359160443591610ea0565b346100e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e7576004357fffffffff0000000000000000000000000000000000000000000000000000000081168091036100e7575f526001602052602060ff60405f2054166040519015158152f35b5f5b8381106105095750505f910152565b81810151838201526020016104fa565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602093610555815180928187528780880191016104f8565b0116010190565b6020808201906020835283518092526040830192602060408460051b8301019501935f915b8483106105915750505050505090565b90919293949584806105cd837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc086600196030187528a51610519565b9801930193019194939290610581565b346100e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e75767ffffffffffffffff6004358181116100e757366023820112156100e75780600401359182116100e7573660248360051b830101116100e75761066191602461065592016111e4565b6040519182918261055c565b0390f35b5f9103126100e757565b346100e7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e757602073ffffffffffffffffffffffffffffffffffffffff60025416604051908152f35b346100e7576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e7576004356106fc816100c9565b60443567ffffffffffffffff81116100e75761071c903690600401610135565b9060c4359160ff831683036100e75761022292610104359260e4359260a435916084359160643591602435906112cb565b346100e75760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e757600435610788816100c9565b60243590610795826100c9565b604435906107a2826100c9565b60045460ff8160a01c16610876577fffffffffffffffffffffff00000000000000000000000000000000000000000074010000000000000000000000000000000000000000926102229573ffffffffffffffffffffffffffffffffffffffff8092167fffffffffffffffffffffffff00000000000000000000000000000000000000006002541617600255169116171760045573ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000006003541617600355565b60046040517f9f4eefba000000000000000000000000000000000000000000000000000000008152fd5b60c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e757600480356108d7816100c9565b602435906108e4826100c9565b67ffffffffffffffff6044358181116100e7576109049036908601610107565b916064359060a4359081116100e7576109209036908801610135565b926109296113c2565b6109547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b3415610aac57813403610a835773ffffffffffffffffffffffffffffffffffffffff6109b0610997895473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1690565b9080821690871603610a5a57803b156100e7575f90604051988980927fd0e30db000000000000000000000000000000000000000000000000000000000825234905af1968715610a5557610a0e97610a3c575b505b60843592611421565b61022260017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f5416175f55565b80610a49610a4f92610307565b80610665565b5f610a03565b610db5565b876040517f3539a701000000000000000000000000000000000000000000000000000000008152fd5b866040517f1841b4e1000000000000000000000000000000000000000000000000000000008152fd5b610a0e9650610abd82303388611606565b610a05565b346100e7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e757602073ffffffffffffffffffffffffffffffffffffffff60035416604051908152f35b346100e7575f60e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100e75760043590610b50826100c9565b60243560443567ffffffffffffffff81116100e757610b73903690600401610135565b9073ffffffffffffffffffffffffffffffffffffffff610b91610165565b94610b9a6113c2565b610bc57fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b1693843b156100e7576040517fd505accf00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101839052606480359082015260ff91909116608482015260a480359082015260c48035908201525f8160e48183895af1610c80575b50610c4f9293610c4a82303384611606565b611765565b610c7d60017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f5416175f55565b80f35b610c4f9350610c8e90610307565b5f92610c38565b73ffffffffffffffffffffffffffffffffffffffff909c9a919b94979295989396999c610cc06113c2565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f5516998a3b156100e7576040517fef55bec6000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481018990526064810193909352608483019390935260a482019b909b5260ff909a1660c48b015260e48a01919091526101048901525f8861012481838a5af1978815610a5557610d7898610da6575b50611421565b6100f860017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f5416175f55565b610daf90610307565b5f610d72565b6040513d5f823e3d90fd5b73ffffffffffffffffffffffffffffffffffffffff909a91999293949596979a610de86113c2565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f551697883b156100e7576040517fd505accf00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101879052606481019b909b5260ff1660848b015260a48a019190915260c4890152610d78975f8160e481838b5af1610e91575b50610e8c83303389611606565b611421565b610e9a90610307565b5f610e7f565b939298919697909497610eb16113c2565b610edc7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b873403610fce57610f0561099760025473ffffffffffffffffffffffffffffffffffffffff1690565b96873b15610fa457873b156100e7575f99610f51956040519c8d9b8c9a8b9a7f7b939232000000000000000000000000000000000000000000000000000000008c523360048d01610ff8565b039134905af18015610a5557610f91575b506100f860017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f5416175f55565b80610a49610f9e92610307565b5f610f62565b60046040517fb474246c000000000000000000000000000000000000000000000000000000008152fd5b60046040517f1841b4e1000000000000000000000000000000000000000000000000000000008152fd5b9794909361107c9b9a969294999793996101809a73ffffffffffffffffffffffffffffffffffffffff8097818094168d521660208c01521660408a01525f60608a0152608089015260a088015260c08701521660e085015263ffffffff92838092166101008601521661012084015216610140820152816101608201520190610519565b90565b67ffffffffffffffff811161031b5760051b60200190565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1813603018212156100e7570180359067ffffffffffffffff82116100e7576020019181360383136100e757565b908210156111305761112c9160051b8101906110c4565b9091565b611097565b908092918237015f815290565b3d1561116c573d9061115382610361565b916111616040519384610320565b82523d5f602084013e565b606090565b6020818303126100e75780519067ffffffffffffffff82116100e7570181601f820112156100e75780516111a481610361565b926111b26040519485610320565b818452602082840101116100e75761107c91602080850191016104f8565b80518210156111305760209160051b010190565b9190916111f08361107f565b9060406112006040519384610320565b8483527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061122d8661107f565b015f5b8181106112ba57505082945f5b81811061124b575050505050565b5f80611258838588611115565b90611267875180938193611135565b0390305af4611274611142565b901561129a579060019161128882886111d0565b5261129381876111d0565b500161123d565b60448151106100e7578060046100e7920151602480918301019101611171565b806060602080938801015201611230565b73ffffffffffffffffffffffffffffffffffffffff9098959897949392979691966112f46113c2565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f551694853b156100e7576040517fef55bec6000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481018890526064810193909352608483019390935260a482019790975260ff90961660c487015260e48601919091526101048501525f846101248183855af1938415610a5557610d78946113ac575b50611765565b6113b590610307565b5f6113a6565b156100e757565b60ff5f5416156100e757565b7fffffffff00000000000000000000000000000000000000000000000000000000903581811693926004811061140357505050565b60040360031b82901b16169150565b908160209103126100e7575190565b95949392919061147561147161146a61143a848b6113ce565b7fffffffff00000000000000000000000000000000000000000000000000000000165f52600160205260405f2090565b5460ff1690565b1590565b6115dc576040517f70a082310000000000000000000000000000000000000000000000000000000080825230600483015260209873ffffffffffffffffffffffffffffffffffffffff94919391929091908a816024818c8a165afa948515610a55578b915f966115bd575b5060405190815230600482015295869060249082908d165afa948515610a55576100f89a5f96611584575b50505f61157f92819261153d8661153760035473ffffffffffffffffffffffffffffffffffffffff1690565b8d6118cf565b8261155d60035473ffffffffffffffffffffffffffffffffffffffff1690565b9261156d60405180948193611135565b03925af1611579611142565b506113bb565b6119d9565b5f929650926115ad83928561157f96903d106115b6575b6115a58183610320565b810190611412565b9692509261150b565b503d61159b565b6115d5919650823d84116115b6576115a58183610320565b945f6114e0565b60046040517f42868c9b000000000000000000000000000000000000000000000000000000008152fd5b9290604051927f23b872dd00000000000000000000000000000000000000000000000000000000602085015273ffffffffffffffffffffffffffffffffffffffff809216602485015216604483015260648201526064815260a081019181831067ffffffffffffffff84111761031b576100f892604052611bb0565b3561107c816100c9565b3561107c816102cc565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093818652868601375f8582860101520116010190565b9895909461107c9d9b97926020956117579a959c976101809d8d73ffffffffffffffffffffffffffffffffffffffff998a8096818096168452169101521660408d01521660608b015260808a015260a089015260c08801521660e086015263ffffffff8092166101008601521661012084015261014083019063ffffffff169052565b816101608201520191611696565b9190916117948361178e61099760025473ffffffffffffffffffffffffffffffffffffffff1690565b836118cf565b6117b661099760025473ffffffffffffffffffffffffffffffffffffffff1690565b916117c360408201611682565b6117cf60608301611682565b916117d981611682565b936117e660a08301611682565b6117f260c0840161168c565b6117fe60e0850161168c565b9061180c610100860161168c565b9261181b6101208701876110c4565b9690958b3b156100e7576040519c8d9b8c809c7f7b939232000000000000000000000000000000000000000000000000000000008252608086013595602001359473ffffffffffffffffffffffffffffffffffffffff16916004019c6118809d6116d4565b03815a5f948591f18015610a55576118955750565b80610a496100f892610307565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b60449192602073ffffffffffffffffffffffffffffffffffffffff604051948580927fdd62ed3e000000000000000000000000000000000000000000000000000000008252306004830152808916602483015286165afa928315610a55575f936119ab575b5082018092116119a6576040517f095ea7b300000000000000000000000000000000000000000000000000000000602082015273ffffffffffffffffffffffffffffffffffffffff9390931660248401526044808401929092529082526100f891906119a1606483610320565b611bb0565b6118a2565b6119c591935060203d6020116115b6576115a58183610320565b915f611934565b919082039182116119a657565b6040517f70a082310000000000000000000000000000000000000000000000000000000080825230600483015273ffffffffffffffffffffffffffffffffffffffff989697959695602095878b16959294939087826024818a5afa8015610a5557611a4b925f91611b93575b506119cc565b978810611b69576040519384523060048501528916928581602481875afa8015610a55578392611a81925f92611b4a57506119cc565b03611b20576100f8977f646284e396b68ff4b4f34e0aa97bcdb9c100f5b44a20da5c475f62703985384191611b18611ace60035473ffffffffffffffffffffffffffffffffffffffff1690565b89611ad88c611682565b9360405195869516998d013592859094939260609273ffffffffffffffffffffffffffffffffffffffff6080840197168352602083015260408201520152565b0390a4611765565b60046040517fd6cf42f0000000000000000000000000000000000000000000000000000000008152fd5b611b62919250883d8a116115b6576115a58183610320565b905f611a45565b60046040517f0492ff87000000000000000000000000000000000000000000000000000000008152fd5b611baa9150893d8b116115b6576115a58183610320565b5f611a45565b73ffffffffffffffffffffffffffffffffffffffff166040516040810181811067ffffffffffffffff82111761031b57611c2b937f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656460205f948594604052818152015260208151910182855af1611c25611142565b91611c64565b8051908115918215611c41575b5050156100e757565b81925090602091810103126100e7576020015180151581036100e7575f80611c38565b9015611c7e57815115611c75575090565b3b156100e75790565b5080519081156100e757602001fdfea2646970667358221220273204c702747fdff0ffe95c6319ba2f475d4a21c4beb991049bd8c21271b20064736f6c63430008170033"; - -type SpokePoolV3PeripheryConstructorParams = - | [signer?: Signer] - | ConstructorParameters; - -const isSuperArgs = ( - xs: SpokePoolV3PeripheryConstructorParams -): xs is ConstructorParameters => xs.length > 1; - -export class SpokePoolV3Periphery__factory extends ContractFactory { - constructor(...args: SpokePoolV3PeripheryConstructorParams) { - if (isSuperArgs(args)) { - super(...args); - } else { - super(_abi, _bytecode, args[0]); - } - } - - override deploy( - _allowedSelectors: BytesLike[], - overrides?: Overrides & { from?: string } - ): Promise { - return super.deploy( - _allowedSelectors, - overrides || {} - ) as Promise; - } - override getDeployTransaction( - _allowedSelectors: BytesLike[], - overrides?: Overrides & { from?: string } - ): TransactionRequest { - return super.getDeployTransaction(_allowedSelectors, overrides || {}); - } - override attach(address: string): SpokePoolV3Periphery { - return super.attach(address) as SpokePoolV3Periphery; - } - override connect(signer: Signer): SpokePoolV3Periphery__factory { - return super.connect(signer) as SpokePoolV3Periphery__factory; - } - - static readonly bytecode = _bytecode; - static readonly abi = _abi; - static createInterface(): SpokePoolV3PeripheryInterface { - return new utils.Interface(_abi) as SpokePoolV3PeripheryInterface; - } - static connect( - address: string, - signerOrProvider: Signer | Provider - ): SpokePoolV3Periphery { - return new Contract( - address, - _abi, - signerOrProvider - ) as SpokePoolV3Periphery; - } -} diff --git a/api/_typechain/factories/SpokePoolV3Periphery__factory.ts b/api/_typechain/factories/SpokePoolV3Periphery__factory.ts new file mode 100644 index 000000000..7555ff9e6 --- /dev/null +++ b/api/_typechain/factories/SpokePoolV3Periphery__factory.ts @@ -0,0 +1,1433 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ + +import { Signer, utils, Contract, ContractFactory, Overrides } from "ethers"; +import type { Provider, TransactionRequest } from "@ethersproject/providers"; +import type { + SpokePoolV3Periphery, + SpokePoolV3PeripheryInterface, +} from "../SpokePoolV3Periphery"; + +const _abi = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "ContractInitialized", + type: "error", + }, + { + inputs: [], + name: "InvalidMsgValue", + type: "error", + }, + { + inputs: [], + name: "InvalidPermit2", + type: "error", + }, + { + inputs: [], + name: "InvalidProxy", + type: "error", + }, + { + inputs: [], + name: "InvalidShortString", + type: "error", + }, + { + inputs: [], + name: "InvalidSignature", + type: "error", + }, + { + inputs: [], + name: "InvalidSignature", + type: "error", + }, + { + inputs: [], + name: "InvalidSignatureLength", + type: "error", + }, + { + inputs: [], + name: "InvalidSpokePool", + type: "error", + }, + { + inputs: [], + name: "InvalidSwapToken", + type: "error", + }, + { + inputs: [], + name: "LeftoverSrcTokens", + type: "error", + }, + { + inputs: [], + name: "MinimumExpectedInputAmount", + type: "error", + }, + { + inputs: [], + name: "NotProxy", + type: "error", + }, + { + inputs: [ + { + internalType: "string", + name: "str", + type: "string", + }, + ], + name: "StringTooLong", + type: "error", + }, + { + anonymous: false, + inputs: [], + name: "EIP712DomainChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "exchange", + type: "address", + }, + { + indexed: false, + internalType: "bytes", + name: "exchangeCalldata", + type: "bytes", + }, + { + 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: [ + { + internalType: "address", + name: "recipient", + type: "address", + }, + { + internalType: "address", + name: "inputToken", + type: "address", + }, + { + internalType: "uint256", + name: "inputAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "outputAmount", + type: "uint256", + }, + { + 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: "exclusivityParameter", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + name: "deposit", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "signatureOwner", + type: "address", + }, + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.Fees", + name: "submissionFees", + type: "tuple", + }, + { + components: [ + { + internalType: "address", + name: "inputToken", + type: "address", + }, + { + 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: "exclusivityParameter", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: + "struct SpokePoolV3PeripheryInterface.BaseDepositData", + name: "baseDepositData", + type: "tuple", + }, + { + internalType: "uint256", + name: "inputAmount", + type: "uint256", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.DepositData", + name: "depositData", + type: "tuple", + }, + { + internalType: "uint256", + name: "validAfter", + type: "uint256", + }, + { + internalType: "uint256", + name: "validBefore", + type: "uint256", + }, + { + internalType: "bytes32", + name: "nonce", + type: "bytes32", + }, + { + internalType: "bytes", + name: "receiveWithAuthSignature", + type: "bytes", + }, + { + internalType: "bytes", + name: "depositDataSignature", + type: "bytes", + }, + ], + name: "depositWithAuthorization", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "signatureOwner", + type: "address", + }, + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.Fees", + name: "submissionFees", + type: "tuple", + }, + { + components: [ + { + internalType: "address", + name: "inputToken", + type: "address", + }, + { + 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: "exclusivityParameter", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: + "struct SpokePoolV3PeripheryInterface.BaseDepositData", + name: "baseDepositData", + type: "tuple", + }, + { + internalType: "uint256", + name: "inputAmount", + type: "uint256", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.DepositData", + name: "depositData", + type: "tuple", + }, + { + internalType: "uint256", + name: "deadline", + type: "uint256", + }, + { + internalType: "bytes", + name: "permitSignature", + type: "bytes", + }, + { + internalType: "bytes", + name: "depositDataSignature", + type: "bytes", + }, + ], + name: "depositWithPermit", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "signatureOwner", + type: "address", + }, + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.Fees", + name: "submissionFees", + type: "tuple", + }, + { + components: [ + { + internalType: "address", + name: "inputToken", + type: "address", + }, + { + 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: "exclusivityParameter", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: + "struct SpokePoolV3PeripheryInterface.BaseDepositData", + name: "baseDepositData", + type: "tuple", + }, + { + internalType: "uint256", + name: "inputAmount", + type: "uint256", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.DepositData", + name: "depositData", + type: "tuple", + }, + { + components: [ + { + components: [ + { + internalType: "address", + name: "token", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + internalType: "struct IPermit2.TokenPermissions", + name: "permitted", + type: "tuple", + }, + { + internalType: "uint256", + name: "nonce", + type: "uint256", + }, + { + internalType: "uint256", + name: "deadline", + type: "uint256", + }, + ], + internalType: "struct IPermit2.PermitTransferFrom", + name: "permit", + type: "tuple", + }, + { + internalType: "bytes", + name: "signature", + type: "bytes", + }, + ], + name: "depositWithPermit2", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "domainSeparator", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "eip712Domain", + outputs: [ + { + internalType: "bytes1", + name: "fields", + type: "bytes1", + }, + { + internalType: "string", + name: "name", + type: "string", + }, + { + internalType: "string", + name: "version", + type: "string", + }, + { + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + internalType: "address", + name: "verifyingContract", + type: "address", + }, + { + internalType: "bytes32", + name: "salt", + type: "bytes32", + }, + { + internalType: "uint256[]", + name: "extensions", + type: "uint256[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "contract V3SpokePoolInterface", + name: "_spokePool", + type: "address", + }, + { + internalType: "contract WETH9Interface", + name: "_wrappedNativeToken", + type: "address", + }, + { + internalType: "address", + name: "_proxy", + type: "address", + }, + { + internalType: "contract IPermit2", + name: "_permit2", + type: "address", + }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "isValidSignature", + outputs: [ + { + internalType: "bytes4", + name: "magicBytes", + type: "bytes4", + }, + ], + 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: [], + name: "permit2", + outputs: [ + { + internalType: "contract IPermit2", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "proxy", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "spokePool", + outputs: [ + { + internalType: "contract V3SpokePoolInterface", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.Fees", + name: "submissionFees", + type: "tuple", + }, + { + components: [ + { + internalType: "address", + name: "inputToken", + type: "address", + }, + { + 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: "exclusivityParameter", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: + "struct SpokePoolV3PeripheryInterface.BaseDepositData", + name: "depositData", + type: "tuple", + }, + { + internalType: "address", + name: "swapToken", + type: "address", + }, + { + internalType: "address", + name: "exchange", + type: "address", + }, + { + internalType: "enum SpokePoolV3PeripheryInterface.TransferType", + name: "transferType", + type: "uint8", + }, + { + internalType: "uint256", + name: "swapTokenAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "minExpectedInputTokenAmount", + type: "uint256", + }, + { + internalType: "bytes", + name: "routerCalldata", + type: "bytes", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.SwapAndDepositData", + name: "swapAndDepositData", + type: "tuple", + }, + ], + name: "swapAndBridge", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "signatureOwner", + type: "address", + }, + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.Fees", + name: "submissionFees", + type: "tuple", + }, + { + components: [ + { + internalType: "address", + name: "inputToken", + type: "address", + }, + { + 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: "exclusivityParameter", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: + "struct SpokePoolV3PeripheryInterface.BaseDepositData", + name: "depositData", + type: "tuple", + }, + { + internalType: "address", + name: "swapToken", + type: "address", + }, + { + internalType: "address", + name: "exchange", + type: "address", + }, + { + internalType: "enum SpokePoolV3PeripheryInterface.TransferType", + name: "transferType", + type: "uint8", + }, + { + internalType: "uint256", + name: "swapTokenAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "minExpectedInputTokenAmount", + type: "uint256", + }, + { + internalType: "bytes", + name: "routerCalldata", + type: "bytes", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.SwapAndDepositData", + name: "swapAndDepositData", + type: "tuple", + }, + { + internalType: "uint256", + name: "validAfter", + type: "uint256", + }, + { + internalType: "uint256", + name: "validBefore", + type: "uint256", + }, + { + internalType: "bytes32", + name: "nonce", + type: "bytes32", + }, + { + internalType: "bytes", + name: "receiveWithAuthSignature", + type: "bytes", + }, + { + internalType: "bytes", + name: "swapAndDepositDataSignature", + type: "bytes", + }, + ], + name: "swapAndBridgeWithAuthorization", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "signatureOwner", + type: "address", + }, + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.Fees", + name: "submissionFees", + type: "tuple", + }, + { + components: [ + { + internalType: "address", + name: "inputToken", + type: "address", + }, + { + 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: "exclusivityParameter", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: + "struct SpokePoolV3PeripheryInterface.BaseDepositData", + name: "depositData", + type: "tuple", + }, + { + internalType: "address", + name: "swapToken", + type: "address", + }, + { + internalType: "address", + name: "exchange", + type: "address", + }, + { + internalType: "enum SpokePoolV3PeripheryInterface.TransferType", + name: "transferType", + type: "uint8", + }, + { + internalType: "uint256", + name: "swapTokenAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "minExpectedInputTokenAmount", + type: "uint256", + }, + { + internalType: "bytes", + name: "routerCalldata", + type: "bytes", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.SwapAndDepositData", + name: "swapAndDepositData", + type: "tuple", + }, + { + internalType: "uint256", + name: "deadline", + type: "uint256", + }, + { + internalType: "bytes", + name: "permitSignature", + type: "bytes", + }, + { + internalType: "bytes", + name: "swapAndDepositDataSignature", + type: "bytes", + }, + ], + name: "swapAndBridgeWithPermit", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "signatureOwner", + type: "address", + }, + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.Fees", + name: "submissionFees", + type: "tuple", + }, + { + components: [ + { + internalType: "address", + name: "inputToken", + type: "address", + }, + { + 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: "exclusivityParameter", + type: "uint32", + }, + { + internalType: "bytes", + name: "message", + type: "bytes", + }, + ], + internalType: + "struct SpokePoolV3PeripheryInterface.BaseDepositData", + name: "depositData", + type: "tuple", + }, + { + internalType: "address", + name: "swapToken", + type: "address", + }, + { + internalType: "address", + name: "exchange", + type: "address", + }, + { + internalType: "enum SpokePoolV3PeripheryInterface.TransferType", + name: "transferType", + type: "uint8", + }, + { + internalType: "uint256", + name: "swapTokenAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "minExpectedInputTokenAmount", + type: "uint256", + }, + { + internalType: "bytes", + name: "routerCalldata", + type: "bytes", + }, + ], + internalType: "struct SpokePoolV3PeripheryInterface.SwapAndDepositData", + name: "swapAndDepositData", + type: "tuple", + }, + { + components: [ + { + components: [ + { + internalType: "address", + name: "token", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + internalType: "struct IPermit2.TokenPermissions", + name: "permitted", + type: "tuple", + }, + { + internalType: "uint256", + name: "nonce", + type: "uint256", + }, + { + internalType: "uint256", + name: "deadline", + type: "uint256", + }, + ], + internalType: "struct IPermit2.PermitTransferFrom", + name: "permit", + type: "tuple", + }, + { + internalType: "bytes", + name: "signature", + type: "bytes", + }, + ], + name: "swapAndBridgeWithPermit2", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "wrappedNativeToken", + outputs: [ + { + internalType: "contract WETH9Interface", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; + +const _bytecode = + "0x61016080604052346200016f57620000178162000173565b60138152602081017f4143524f53532d56332d50455249504845525900000000000000000000000000815260405191620000518362000173565b6005835260208301640312e302e360dc1b8152600160ff195f5416175f556200007a826200018f565b926101209384526200008c8562000358565b92610140938452519020938460e05251902091610100938385524660a0526040519360208501917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8352604086015260608501524660808501523060a085015260a0845260c084019380851060018060401b038611176200015b57846040525190206080523060c052613ead9384620005068539608051846138f5015260a051846139aa015260c051846138c6015260e051846139440152518361396a01525182610aef01525181610b180152f35b634e487b7160e01b5f52604160045260245ffd5b5f80fd5b604081019081106001600160401b038211176200015b57604052565b805160209081811015620002295750601f825111620001ca5780825192015190808310620001bc57501790565b825f19910360031b1b161790565b90604051809263305a27a960e01b82528060048301528251908160248401525f935b8285106200020f575050604492505f838284010152601f80199101168101030190fd5b8481018201518686016044015293810193859350620001ec565b9192916001600160401b0381116200015b5760019182548381811c911680156200034d575b828210146200033957601f811162000303575b5080601f83116001146200029f5750819293945f9262000293575b50505f19600383901b1c191690821b17905560ff90565b015190505f806200027c565b90601f19831695845f52825f20925f905b888210620002eb5750508385969710620002d2575b505050811b01905560ff90565b01515f1960f88460031b161c191690555f8080620002c5565b808785968294968601518155019501930190620002b0565b835f5283601f835f20920160051c820191601f850160051c015b8281106200032d57505062000261565b5f81550184906200031d565b634e487b7160e01b5f52602260045260245ffd5b90607f16906200024e565b805160209081811015620003e45750601f825111620003855780825192015190808310620001bc57501790565b90604051809263305a27a960e01b82528060048301528251908160248401525f935b828510620003ca575050604492505f838284010152601f80199101168101030190fd5b8481018201518686016044015293810193859350620003a7565b906001600160401b0382116200015b57600254926001938481811c91168015620004fa575b838210146200033957601f8111620004c3575b5081601f84116001146200045b57509282939183925f946200044f575b50501b915f199060031b1c19161760025560ff90565b015192505f8062000439565b919083601f19811660025f52845f20945f905b88838310620004a857505050106200048f575b505050811b0160025560ff90565b01515f1960f88460031b161c191690555f808062000481565b8587015188559096019594850194879350908101906200046e565b60025f5284601f845f20920160051c820191601f860160051c015b828110620004ee5750506200041c565b5f8155018590620004de565b90607f16906200040956fe60806040526004361015610011575f80fd5b5f3560e01c806312261ee7146101245780631626ba7e1461011f57806317fcb39b1461011a57806327e98fbd146101155780632e9b8eff146101105780633cbdb8771461010b57806364b39f181461010657806384b0196e1461010157806386b13dae146100fc578063894adf30146100f7578063ac9650d8146100f2578063afdac3d6146100ed578063ec556889146100e8578063f698da25146100e3578063f8c8765e146100de578063f9760e41146100d95763f980d868146100d4575f80fd5b611650565b611479565b61119f565b61115f565b61110e565b6110bd565b611039565b610df8565b610be9565b610ab9565b6109b0565b6106b7565b6105d5565b610506565b6102c1565b6101b6565b610137565b5f91031261013357565b5f80fd5b34610133575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261013357602073ffffffffffffffffffffffffffffffffffffffff60055416604051908152f35b9181601f840112156101335782359167ffffffffffffffff8311610133576020838186019501011161013357565b346101335760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101335760243567ffffffffffffffff811161013357610205903690600401610188565b505073ffffffffffffffffffffffffffffffffffffffff600554163314806102b2575b15610289576102857f1626ba7e000000000000000000000000000000000000000000000000000000005b6040517fffffffff0000000000000000000000000000000000000000000000000000000090911681529081906020820190565b0390f35b6102857fffffffff00000000000000000000000000000000000000000000000000000000610252565b5060ff60065460d81c16610228565b34610133575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261013357602073ffffffffffffffffffffffffffffffffffffffff60045416604051908152f35b73ffffffffffffffffffffffffffffffffffffffff81160361013357565b63ffffffff81160361013357565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b67ffffffffffffffff811161037f57604052565b61033e565b6040810190811067ffffffffffffffff82111761037f57604052565b6080810190811067ffffffffffffffff82111761037f57604052565b6060810190811067ffffffffffffffff82111761037f57604052565b6020810190811067ffffffffffffffff82111761037f57604052565b60a0810190811067ffffffffffffffff82111761037f57604052565b60c0810190811067ffffffffffffffff82111761037f57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761037f57604052565b6040519061047a82610384565b565b6040519061047a826103a0565b6040519061047a826103bc565b67ffffffffffffffff811161037f57601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b9291926104dc82610496565b916104ea604051938461042c565b829481845281830111610133578281602093845f960137010152565b6101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101335760043561053d81610312565b60243561054981610312565b60a43561055581610312565b60c43561056181610330565b60e4359061056e82610330565b610104359261057c84610330565b610124359567ffffffffffffffff87116101335736602388011215610133576105b26105c49736906024816004013591016104d0565b956084359160643591604435916117ec565b005b90816101209103126101335790565b346101335760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101335760043561061081610312565b67ffffffffffffffff90602435828111610133576106329036906004016105c6565b60a4358381116101335761064a903690600401610188565b9060c435948511610133576106666105c4953690600401610188565b9490936084359160643591604435916119ac565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbc608091011261013357604490565b908160809103126101335790565b346101335760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610133576004356106f281610312565b67ffffffffffffffff90602435828111610133576107149036906004016106a9565b6064358381116101335761072c903690600401610188565b909360843590811161013357610749610784913690600401610188565b9290956107546127a6565b61077f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b6127b2565b949192909560408501936107a061079b8688611b79565b611b30565b976060870135978735916107b660208a01611b30565b936107c18b85611b67565b9173ffffffffffffffffffffffffffffffffffffffff8d1693843b15610133576040517fd505accf00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8b166004820152306024820152604480820186905235606482015260ff91909116608482015260a481019290925260c48201525f8160e48183875af1610997575b5086309061086a93613406565b6108749189612812565b61087d85613470565b61088693612a64565b6108908183611b79565b60600161089c90611b30565b916108a78282611b79565b6080016108b390611b30565b936108be8383611b79565b6020016108ca90611b30565b906108d58484611b79565b604001356108e38585611b79565b60a00135906108f28686611b79565b60c0016108fe90611b30565b926109098787611b79565b60e00161091590611bac565b946109208888611b79565b6101000161092d90611bac565b966109388982611b79565b6101200161094590611bac565b9861094f91611b79565b610140810161095d91611bb6565b9a6109699c919a6134e8565b6105c460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f5416175f55565b806109a46109aa9261036b565b80610129565b5f61085d565b346101335760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610133576004356109eb81610312565b67ffffffffffffffff9060243582811161013357610a0d9036906004016106a9565b60a43583811161013357610a25903690600401610188565b9060c43594851161013357610a416105c4953690600401610188565b949093608435916064359160443591611c07565b5f5b838110610a665750505f910152565b8181015183820152602001610a57565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602093610ab281518092818752878088019101610a55565b0116010190565b34610133575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261013357610b8c610b137f0000000000000000000000000000000000000000000000000000000000000000613663565b610b3c7f0000000000000000000000000000000000000000000000000000000000000000613798565b60405190610b49826103d8565b5f8252610b9a6020916040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e0602088015260e0870190610a76565b908582036040870152610a76565b4660608501523060808501525f60a085015283810360c0850152602080845192838152019301915f5b828110610bd257505050500390f35b835185528695509381019392810192600101610bc3565b346101335760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261013357600435610c2481610312565b67ffffffffffffffff60243581811161013357610c459036906004016106a9565b90610c4f3661067a565b9060c43590811161013357610c68903690600401610188565b610c739491946127a6565b610c9e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b610ca784613470565b91843593606086013596610cbb8689611b67565b90610cc461046d565b308152916020830152610d08610cef60055473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1690565b92610d116121fc565b843b15610133575f968793610d56926040519a8b998a9889977f137c29fe000000000000000000000000000000000000000000000000000000008952600489016122ec565b03925af18015610df357610de0575b506040820190610d758284611b79565b610d7e90611b30565b90610d8b60208501611b30565b610d9492612812565b610d9e8183611b79565b606001610daa90611b30565b90610db58184611b79565b608001610dc190611b30565b92610dcc8282611b79565b610dd590611b30565b946108be8383611b79565b806109a4610ded9261036b565b5f610d65565b6119a1565b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610133576004803567ffffffffffffffff811161013357610e4290369083016105c6565b610e4a6127a6565b610e757fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b3415610f855760c08101353403610f5c57610e9260608201611b30565b610eb3610cef845473ffffffffffffffffffffffffffffffffffffffff1690565b9073ffffffffffffffffffffffffffffffffffffffff808316911603610f3357803b15610133575f90604051938480927fd0e30db000000000000000000000000000000000000000000000000000000000825234905af1918215610df35761096992610f20575b50612c31565b806109a4610f2d9261036b565b5f610f1a565b826040517f3539a701000000000000000000000000000000000000000000000000000000008152fd5b506040517f1841b4e1000000000000000000000000000000000000000000000000000000008152fd5b6109699150610f92613864565b610fb3610fa4610cef60608401611b30565b60c08301359030903390613406565b612c31565b6020808201906020835283518092526040830192602060408460051b8301019501935f915b848310610fed5750505050505090565b9091929394958480611029837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc086600196030187528a51610a76565b9801930193019194939290610fdd565b346101335760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101335767ffffffffffffffff6004358181116101335736602382011215610133578060040135918211610133573660248360051b83010111610133576102859160246110b1920161247a565b60405191829182610fb8565b34610133575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261013357602073ffffffffffffffffffffffffffffffffffffffff60035416604051908152f35b34610133575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261013357602073ffffffffffffffffffffffffffffffffffffffff60065416604051908152f35b34610133575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101335760206111976138af565b604051908152f35b346101335760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610133576004356111da81610312565b6024356111e681610312565b604435906111f382610312565b6064359261120084610312565b6112086127a6565b6112337fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b60065460d01c60ff1661144f5761128a7a0100000000000000000000000000000000000000000000000000007fffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffff6006541617600655565b73ffffffffffffffffffffffffffffffffffffffff918183163b15611425576112f16113329273ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000006003541617600355565b73ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000006004541617600455565b813b156113fb576113816113889273ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000006006541617600655565b82163b1590565b6113d1576109699073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000006005541617600555565b60046040517f32d1c8da000000000000000000000000000000000000000000000000000000008152fd5b60046040517fb9e5cf7c000000000000000000000000000000000000000000000000000000008152fd5b60046040517fb474246c000000000000000000000000000000000000000000000000000000008152fd5b60046040517f9f4eefba000000000000000000000000000000000000000000000000000000008152fd5b346101335760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610133576004356114b481610312565b67ffffffffffffffff90602435828111610133576114d69036906004016105c6565b90606435838111610133576114ef903690600401610188565b90936084359081116101335761150c611542913690600401610188565b9290956115176127a6565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f556127b2565b9591939061155260608701611b30565b86359061156160208901611b30565b9061157060c08a013584611b67565b9973ffffffffffffffffffffffffffffffffffffffff82169a8b3b15610133576040517fd505accf00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff88166004820152306024820152604480820183905235606482015260ff92909216608483015260a482019990995260c481019490945261096999610fb39861162e9561162992905f8160e48183865af161163d575b50873091613406565b612812565b61163785612964565b90612a64565b806109a461164a9261036b565b5f611620565b346101335760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101335760043561168b81610312565b67ffffffffffffffff90602435828111610133576116ad9036906004016105c6565b906116b73661067a565b9260c435908111610133576116d0903690600401610188565b90916116da6127a6565b6117057fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b61170e84612964565b948435936117208560c0880135611b67565b9061172961046d565b308152916020830152611754610cef60055473ffffffffffffffffffffffffffffffffffffffff1690565b9261175d6126ed565b843b15610133575f9687936117a2926040519c8d998a9889977f137c29fe000000000000000000000000000000000000000000000000000000008952600489016122ec565b03925af1908115610df35761096993610fb3926117d9575b506117c760608401611b30565b6117d360208501611b30565b90612812565b806109a46117e69261036b565b5f6117ba565b9392989196979094976117fd6127a6565b6118287fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b8734036118f057611851610cef60035473ffffffffffffffffffffffffffffffffffffffff1690565b96873b1561142557873b15610133575f9961189d956040519c8d9b8c9a8b9a7f7b939232000000000000000000000000000000000000000000000000000000008c523360048d0161191a565b039134905af18015610df3576118dd575b5061047a60017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f5416175f55565b806109a46118ea9261036b565b5f6118ae565b60046040517f1841b4e1000000000000000000000000000000000000000000000000000000008152fd5b9794909361199e9b9a969294999793996101809a73ffffffffffffffffffffffffffffffffffffffff8097818094168d521660208c01521660408a01525f60608a0152608089015260a088015260c08701521660e085015263ffffffff92838092166101008601521661012084015216610140820152816101608201520190610a76565b90565b6040513d5f823e3d90fd5b9291959794909693976119bd6127a6565b6119e87fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b6119f1916127b2565b88359960608a01989293611a048a611b30565b73ffffffffffffffffffffffffffffffffffffffff1692611a298d60c08e0135611b67565b92843b15610133576040517fef55bec600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8a16600482015230602482015260448101949094526064840195909552608483019690965260a482019390935260ff90941660c485015260e48401919091526101048301919091525f90829061012490829084905af1968715610df357611ae3610fb39661162e93611aef9a611b1d575b50611b30565b6117d360208901611b30565b61047a60017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f5416175f55565b806109a4611b2a9261036b565b5f611add565b3561199e81610312565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b91908201809211611b7457565b611b3a565b9035907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffea181360301821215610133570190565b3561199e81610330565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610133570180359067ffffffffffffffff82116101335760200191813603831361013357565b94919381979391969894611c196127a6565b611c447fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005f54165f55565b6060880135998835948591611c58916127b2565b8d60408d97949397019c8d611c6c91611b79565b611c7590611b30565b73ffffffffffffffffffffffffffffffffffffffff1694611c9591611b67565b92843b15610133576040517fef55bec600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8d16600482015230602482015260448101949094526064840195909552608483019690965260a482019390935260ff90941660c485015260e48401919091526101048301919091525f90829061012490829084905af18015610df357611e6c575b50611d448587611b79565b611d4d90611b30565b90611d5a60208801611b30565b611d6392612812565b611d6c85613470565b611d7593612a64565b611d7f8183611b79565b606001611d8b90611b30565b90611d968184611b79565b608001611da290611b30565b92611dad8282611b79565b611db690611b30565b94611dc18383611b79565b602001611dcd90611b30565b90611dd88484611b79565b60400135611de68585611b79565b60a0013590611df58686611b79565b60c001611e0190611b30565b92611e0c8787611b79565b60e001611e1890611bac565b94611e238888611b79565b61010001611e3090611bac565b96611e3b8982611b79565b61012001611e4890611bac565b98611e5291611b79565b6101408101611e6091611bb6565b9a611aef9c919a6134e8565b806109a4611e799261036b565b5f611d39565b67ffffffffffffffff811161037f5760051b60200190565b6040517f466565732800000000000000000000000000000000000000000000000000000060208201527f75696e7432353620616d6f756e7400000000000000000000000000000000000060258201527f6164647265737320726563697069656e7429000000000000000000000000000060338201526025815261199e816103bc565b6040517f426173654465706f73697444617461280000000000000000000000000000000060208201527f6164647265737320696e707574546f6b656e000000000000000000000000000060308201527f61646472657373206f7574707574546f6b656e0000000000000000000000000060428201527f75696e74323536206f7574707574416d6f756e7400000000000000000000000060558201527f61646472657373206465706f7369746f7200000000000000000000000000000060698201527f6164647265737320726563697069656e74000000000000000000000000000000607a8201527f75696e743235362064657374696e6174696f6e436861696e4964000000000000608b8201527f61646472657373206578636c757369766552656c61796572000000000000000060a58201527f75696e7433322071756f746554696d657374616d70000000000000000000000060bd8201527f75696e7433322066696c6c446561646c696e650000000000000000000000000060d28201527f75696e743332206578636c75736976697479506172616d65746572000000000060e58201527f6279746573206d6573736167652900000000000000000000000000000000000061010082015261199e8161010e81015b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810183528261042c565b6040517f4465706f736974446174612846656573207375626d697373696f6e466565732c60208201527f426173654465706f7369744461746120626173654465706f736974446174612c60408201527f75696e7432353620696e707574416d6f756e742900000000000000000000000060608201526054815261199e816103a0565b604051906121aa826103bc565b602e82527f696e7432353620616d6f756e74290000000000000000000000000000000000006040837f546f6b656e5065726d697373696f6e73286164647265737320746f6b656e2c7560208201520152565b612204611e97565b61199e6034612211611f19565b9261221a61211b565b61222261219d565b906040519586937f4465706f73697444617461207769746e6573732900000000000000000000000060208601526122628151809260208989019101610a55565b84016122778251809360208985019101610a55565b0161228b8251809360208885019101610a55565b0161229f8251809360208785019101610a55565b0103601481018452018261042c565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093818652868601375f8582860101520116010190565b959361235b61236f94602061199e9a989561014095606081359161230f83610312565b73ffffffffffffffffffffffffffffffffffffffff9283168e5280850135858f0152604080820135908f0152013560608d01528151811660808d015291015160a08b01521660c0890152565b60e087015280610100870152850190610a76565b926101208185039101526122ae565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b908210156123c6576123c29160051b810190611bb6565b9091565b61237e565b908092918237015f815290565b3d15612402573d906123e982610496565b916123f7604051938461042c565b82523d5f602084013e565b606090565b6020818303126101335780519067ffffffffffffffff8211610133570181601f8201121561013357805161243a81610496565b92612448604051948561042c565b818452602082840101116101335761199e9160208085019101610a55565b80518210156123c65760209160051b010190565b91909161248683611e7f565b906040612496604051938461042c565b8483527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06124c386611e7f565b015f5b81811061255057505082945f5b8181106124e1575050505050565b5f806124ee8385886123ab565b906124fd8751809381936123cb565b0390305af461250a6123d8565b9015612530579060019161251e8288612466565b526125298187612466565b50016124d3565b604481511061013357806004610133920151602480918301019101612407565b8060606020809388010152016124c6565b6040517f53776170416e644465706f73697444617461280000000000000000000000000060208201527f46656573207375626d697373696f6e466565730000000000000000000000000060338201527f426173654465706f73697444617461206465706f73697444617461000000000060468201527f616464726573732073776170546f6b656e00000000000000000000000000000060618201527f616464726573732065786368616e67650000000000000000000000000000000060728201527f5472616e7366657254797065207472616e73666572547970650000000000000060828201527f75696e743235362073776170546f6b656e416d6f756e74000000000000000000609b8201527f75696e74323536206d696e4578706563746564496e707574546f6b656e416d6f60b28201527f756e74000000000000000000000000000000000000000000000000000000000060d28201527f627974657320726f7574657243616c6c6461746129000000000000000000000060d582015261199e8160ea81016120ef565b6126f5611e97565b61199e603b612702611f19565b9261270b612561565b61271361219d565b906040519586937f53776170416e644465706f73697444617461207769746e65737329000000000060208601526127538151809260208989019101610a55565b84016127688251809360208985019101610a55565b0161277c8251809360208885019101610a55565b016127908251809360208785019101610a55565b0103601b81018452018261042c565b1561013357565b60ff5f54161561013357565b919091604183036127e85782604010156123c657604081013560f81c908360201161013357803593604011610133576020013591565b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b91908161281e57505050565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000602082015273ffffffffffffffffffffffffffffffffffffffff918216602482015260448082019390935291825261047a9261287f60648461042c565b16613cad565b916128ae906128a061199e9593606086526060860190610a76565b908482036020860152610a76565b916040818403910152610a76565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600311156128f357565b6128bc565b3560038110156101335790565b9596939290999897949161012087019a87526020870152604086015273ffffffffffffffffffffffffffffffffffffffff809216606086015216608084015260038410156128f3576101009360a084015260c083015260e08201520152565b61296c612561565b90612a5e612978611e97565b92612981611f19565b93612996604051958692602084019485612885565b03936129c87fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09586810183528261042c565b519020926129d5836139d0565b93612a526129ee6129e96040870187611b79565b613a39565b916129fb60608701611b30565b95612a0860808201611b30565b91612a1560a083016128f8565b612a2d612a26610100850185611bb6565b36916104d0565b6020815191012093604051998a97602089019c8d9560c060e089013598013596612905565b0390810183528261042c565b51902090565b92916042612aaf92612a746138af565b90604051917f1901000000000000000000000000000000000000000000000000000000000000835260028301526022820152209236916104d0565b91612aba8383613d95565b60058195929510156128f357159384612b7e575b508315612ae0575b505050156127e857565b5f929350908291604051612b31816120ef60208201947f1626ba7e00000000000000000000000000000000000000000000000000000000998a87526024840152604060448401526064830190610a76565b51915afa90612b3e6123d8565b82612b70575b82612b54575b50505f8080612ad6565b9091506020818051810103126101335760200151145f80612b4a565b915060208251101591612b44565b73ffffffffffffffffffffffffffffffffffffffff83811691161493505f612ace565b90816020910312610133575190565b65ffffffffffff809116908114611b745760010190565b90816020910312610133575180151581036101335790565b91908203918211611b7457565b92916080949796959273ffffffffffffffffffffffffffffffffffffffff612c229316855260a0602086015260a08501916122ae565b95604083015260608201520152565b612c40610cef60608301611b30565b906040808201612c56610cef61079b8386611b79565b92612c6360a082016128f8565b92612c7060808301611b30565b9060c08301359673ffffffffffffffffffffffffffffffffffffffff808216918351997f70a082310000000000000000000000000000000000000000000000000000000098898c528b60209b8c60049280612cea3086830191909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b03818a5afa9d8e15610df3575f9e6133e5575b5085169c8d928d8d8a5195869182528180612d373089830191909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b03915afa8015610df3578f918f908f90988b8f988f9b8f915f976133c6575b50612d60816128e9565b8061301257505050888a612d7392613b56565b8b6101009d8e89019889612d8691611bb6565b90925192838092612d96926123cb565b035a925f8094938194f1612da86123d8565b50612db29061279f565b8b5190815230868201908152909384918290819060200103915afa8015610df3578f92612de7935f92612ff5575b5050612bdf565b9b60e08b01358d10612fcd578851908152308382019081528e908290819060200103818b5afa8015610df35785928f92612e28935f93612f9e575050612bdf565b03612f7757508a9492888b8e969489612e428e9685611bb6565b94909a612e4f8883611b79565b01612e5990611b30565b96612e6391611b79565b0135928a519687961699612e779587612bec565b037fa914d6534898f72d14dd30011bb127105e9a394a611c88cd9f37ba7c7dff6cfa91a4612ea58484611b79565b606001612eb190611b30565b94612ebc8585611b79565b608001612ec890611b30565b96612ed38686611b79565b01612edd90611b30565b91612ee88686611b79565b0135612ef48686611b79565b60a0013591612f038787611b79565b60c001612f0f90611b30565b93612f1a8888611b79565b60e001612f2690611bac565b95612f318989611b79565b01612f3b90611bac565b96612f468982611b79565b61012001612f5390611bac565b98612f5d91611b79565b6101408101612f6b91611bb6565b9a61047a9c919a6134e8565b86517fd6cf42f0000000000000000000000000000000000000000000000000000000008152fd5b612fbe929350803d10612fc6575b612fb6818361042c565b810190612ba1565b908f80612de0565b503d612fac565b8289517f0492ff87000000000000000000000000000000000000000000000000000000008152fd5b61300b9250803d10612fc657612fb6818361042c565b8f80612de0565b9096506001919799508a935061302a819c969c6128e9565b036130dc5750505050905051917fa9059cbb0000000000000000000000000000000000000000000000000000000083528c838061308d878d8784016020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b03815f8b5af18015610df35789958f918f908f90968e976130af575b50612d73565b6130ce90833d85116130d5575b6130c6818361042c565b810190612bc7565b505f6130a9565b503d6130bc565b6132829392916131119161310b610cef60059b999b5473ffffffffffffffffffffffffffffffffffffffff1690565b90613b56565b61315c7b010000000000000000000000000000000000000000000000000000007fffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffff6006541617600655565b61325a8a61324b613185610cef60055473ffffffffffffffffffffffffffffffffffffffff1690565b9661323c61319e60065465ffffffffffff9060a01c1690565b936131f76131ab86612bb0565b7fffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff79ffffffffffff00000000000000000000000000000000000000006006549260a01b16911617600655565b61321e61320261047c565b73ffffffffffffffffffffffffffffffffffffffff909e168e52565b8d1673ffffffffffffffffffffffffffffffffffffffff16868d0152565b4265ffffffffffff16908b0152565b65ffffffffffff166060890152565b613262610489565b96875286019073ffffffffffffffffffffffffffffffffffffffff169052565b4289850152803b15610133576133665f949185928b519687809481937f2b67b570000000000000000000000000000000000000000000000000000000008352308a8401906002906040610140946101009273ffffffffffffffffffffffffffffffffffffffff8091168652815181815116602088015281602082015116848801526060848201519165ffffffffffff809316828a0152015116608087015260208201511660a0860152015160c08401528060e08401528201527f30780000000000000000000000000000000000000000000000000000000000006101208201520190565b03925af18015610df35789958f918f908f90968e976133b3575b506133ae7fffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffff60065416600655565b612d73565b806109a46133c09261036b565b5f613380565b6133de919750863d8811612fc657612fb6818361042c565b955f612d56565b86919e506133ff908e803d10612fc657612fb6818361042c565b9d90612cfd565b909261047a93604051937f23b872dd00000000000000000000000000000000000000000000000000000000602086015273ffffffffffffffffffffffffffffffffffffffff809216602486015216604484015260648301526064825261346b826103f4565b613cad565b61347861211b565b613480611e97565b6134a061348b611f19565b916120ef604051938492602084019687612885565b5190209060606134af826139d0565b916134c06129e96040830183611b79565b60405193602085019586526040850152828401520135608082015260808152612a5e816103f4565b9894909995919b96929a979361351a818e73ffffffffffffffffffffffffffffffffffffffff80600354169116613b56565b73ffffffffffffffffffffffffffffffffffffffff600354169b8c3b15610133576040519d8e9c8d809d7f7b93923200000000000000000000000000000000000000000000000000000000825273ffffffffffffffffffffffffffffffffffffffff16906004015273ffffffffffffffffffffffffffffffffffffffff1660248d015273ffffffffffffffffffffffffffffffffffffffff1660448c015273ffffffffffffffffffffffffffffffffffffffff1660648b015260848a015260a489015260c488015273ffffffffffffffffffffffffffffffffffffffff1660e487015261010486016136109163ffffffff169052565b63ffffffff1661012485015263ffffffff1661014484015261016483016101809052610184830190613641926122ae565b03815a5f948591f18015610df3576136565750565b806109a461047a9261036b565b60ff81146136745761199e90613d47565b506040516001805480821c915f9082811690811561378e575b602090602086108314613761578587528694602086019390811561372357506001146136c3575b50505061199e9250038261042c565b9250936136f160015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf690565b945f935b82851061370d5750505061199e9350015f80806136b4565b86548585015295860195879550938101936136f5565b91505061199e959293507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009150168252151560051b015f80806136b4565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b93607f169361368d565b60ff81146137a95761199e90613d47565b506040515f6002546001918160011c926001831690811561385a575b602090602086108314613761578587528694602086019390811561372357506001146137fa5750505061199e9250038261042c565b92509361382860025f527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90565b945f935b8285106138445750505061199e9350015f80806136b4565b865485850152958601958795509381019361382c565b93607f16936137c5565b73ffffffffffffffffffffffffffffffffffffffff60065416330361388557565b60046040517fbf10dd3a000000000000000000000000000000000000000000000000000000008152fd5b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163014806139a7575b15613917577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f000000000000000000000000000000000000000000000000000000000000000060408201527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260a08152612a5e81610410565b507f000000000000000000000000000000000000000000000000000000000000000046146138ee565b6139d8611e97565b602081519101209073ffffffffffffffffffffffffffffffffffffffff6020820135613a0381610312565b604051926020840194855235604084015216606082015260608152612a5e816103a0565b613a2f611f19565b6020815191012090565b613a41613a27565b612a5e613a4d83611b30565b926120ef613a5d60208301611b30565b91613a6a60608201611b30565b90613a7760808201611b30565b90613a8460c08201611b30565b613a9060e08301611bac565b90613a9e6101008401611bac565b92613aac6101208201611bac565b94613abe612a26610140840184611bb6565b8051602091820120604080519283019d8e5273ffffffffffffffffffffffffffffffffffffffff9e8f16838201529a8e166060830152998301356080820152968c1660a080890191909152908c1660c0880152013560e086015290981661010084015263ffffffff978816610120840152871661014083015290951661016086015261018085019190915291929182906101a0820190565b6040517f095ea7b3000000000000000000000000000000000000000000000000000000006020820181815273ffffffffffffffffffffffffffffffffffffffff851660248401526044808401969096529482529390927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe091613bd960648661042c565b5f8073ffffffffffffffffffffffffffffffffffffffff86169287519082855af190613c036123d8565b82613c7b575b5081613c70575b5015613c1e575b5050505050565b604051602081019590955273ffffffffffffffffffffffffffffffffffffffff1660248501525f6044850152613c669361346b91613c60908260648101612a52565b82613cad565b5f80808080613c17565b90503b15155f613c10565b80519192508115918215613c93575b5050905f613c09565b613ca69250602080918301019101612bc7565b5f80613c8a565b905f8073ffffffffffffffffffffffffffffffffffffffff613d179416927f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65646020604051613cfa81610384565b818152015260208151910182855af1613d116123d8565b91613e4e565b8051908115918215613d2d575b50501561013357565b613d409250602080918301019101612bc7565b5f80613d24565b60ff811690601f8211613d6b5760405191613d6183610384565b8252602082015290565b60046040517fb3512b0c000000000000000000000000000000000000000000000000000000008152fd5b9060418151145f14613dbd576123c291602082015190606060408401519301515f1a90613dc6565b50505f90600290565b7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411613e43576020935f9360ff60809460405194855216868401526040830152606082015282805260015afa15610df3575f5173ffffffffffffffffffffffffffffffffffffffff811615613e3b57905f90565b505f90600190565b505050505f90600390565b9015613e6857815115613e5f575090565b3b156101335790565b50805190811561013357602001fdfea2646970667358221220095447cfdf911840a498e253cc4606b4cc497e7eaa081a2c8d529439a511e86164736f6c63430008170033"; + +type SpokePoolV3PeripheryConstructorParams = + | [signer?: Signer] + | ConstructorParameters; + +const isSuperArgs = ( + xs: SpokePoolV3PeripheryConstructorParams +): xs is ConstructorParameters => xs.length > 1; + +export class SpokePoolV3Periphery__factory extends ContractFactory { + constructor(...args: SpokePoolV3PeripheryConstructorParams) { + if (isSuperArgs(args)) { + super(...args); + } else { + super(_abi, _bytecode, args[0]); + } + } + + override deploy( + overrides?: Overrides & { from?: string } + ): Promise { + return super.deploy(overrides || {}) as Promise; + } + override getDeployTransaction( + overrides?: Overrides & { from?: string } + ): TransactionRequest { + return super.getDeployTransaction(overrides || {}); + } + override attach(address: string): SpokePoolV3Periphery { + return super.attach(address) as SpokePoolV3Periphery; + } + override connect(signer: Signer): SpokePoolV3Periphery__factory { + return super.connect(signer) as SpokePoolV3Periphery__factory; + } + + static readonly bytecode = _bytecode; + static readonly abi = _abi; + static createInterface(): SpokePoolV3PeripheryInterface { + return new utils.Interface(_abi) as SpokePoolV3PeripheryInterface; + } + static connect( + address: string, + signerOrProvider: Signer | Provider + ): SpokePoolV3Periphery { + return new Contract( + address, + _abi, + signerOrProvider + ) as SpokePoolV3Periphery; + } +} diff --git a/api/_utils.ts b/api/_utils.ts index ded372730..1fe12c9ea 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -84,7 +84,6 @@ import { TokenNotFoundError, } from "./_errors"; import { Token } from "./_dexes/types"; -import { addMarkupToAmount } from "./_dexes/uniswap/utils"; export { InputError, handleErrorCondition } from "./_errors"; export const { Profiler } = sdk.utils; @@ -908,7 +907,7 @@ export async function getBridgeQuoteForMinOutput(params: { outputToken: params.outputToken.address, originChainId: params.inputToken.chainId, destinationChainId: params.outputToken.chainId, - skipAmountLimit: false, + skipAmountLimit: true, recipient: params.recipient, message: params.message, }; @@ -2385,3 +2384,9 @@ export function getSpokePoolVerifier(chainId: number) { const address = ENABLED_ROUTES.spokePoolVerifier.address; return SpokePoolVerifier__factory.connect(address, getProvider(chainId)); } + +export function addMarkupToAmount(amount: BigNumber, markup = 0.01) { + return amount + .mul(ethers.utils.parseEther((1 + Number(markup)).toString())) + .div(sdk.utils.fixedPointAdjustment); +} diff --git a/api/swap-quote.ts b/api/swap-quote.ts index 073340abb..cfca8f8c0 100644 --- a/api/swap-quote.ts +++ b/api/swap-quote.ts @@ -16,7 +16,7 @@ import { import { getUniswapQuoteWithSwapQuoter } from "./_dexes/uniswap/swap-quoter"; import { get1inchQuoteForOriginSwapExactInput } from "./_dexes/1inch"; import { InvalidParamError } from "./_errors"; -import { AMOUNT_TYPE } from "./_dexes/cross-swap"; +import { AMOUNT_TYPE } from "./_dexes/cross-swap-service"; const SwapQuoteQueryParamsSchema = type({ swapToken: validAddress(), diff --git a/api/swap/_utils.ts b/api/swap/_utils.ts index e1c535899..055b7093d 100644 --- a/api/swap/_utils.ts +++ b/api/swap/_utils.ts @@ -11,11 +11,6 @@ import { getWrappedNativeTokenAddress, getCachedTokenPrice, } from "../_utils"; -import { - AMOUNT_TYPE, - getCrossSwapQuotes, - AmountType, -} from "../_dexes/cross-swap"; import { InvalidParamError } from "../_errors"; import { isValidIntegratorId } from "../_integrator-id"; import { @@ -23,8 +18,9 @@ import { CrossSwapQuotes, SwapQuote, Token, + AmountType, } from "../_dexes/types"; -import { formatUnits } from "ethers/lib/utils"; +import { AMOUNT_TYPE } from "../_dexes/utils"; import { encodeApproveCalldata } from "../_multicall-handler"; export const BaseSwapQueryParamsSchema = type({ @@ -103,7 +99,6 @@ export async function handleBaseSwapQueryParams( const amountType = tradeType as AmountType; const amount = BigNumber.from(_amount); - // 1. Get token details const [inputToken, outputToken] = await Promise.all([ getCachedTokenInfo({ address: inputTokenAddress, @@ -115,32 +110,20 @@ export async function handleBaseSwapQueryParams( }), ]); - // 2. Get swap quotes and calldata based on the swap type - const crossSwapQuotes = await getCrossSwapQuotes({ - amount, + return { inputToken, outputToken, - depositor, - recipient: recipient || depositor, - slippageTolerance: Number(slippageTolerance), - type: amountType, + amount, + amountType, refundOnOrigin, - refundAddress, - isInputNative, - isOutputNative, - }); - - // 3. Calculate fees based for full route - // const fees = await calculateCrossSwapFees(crossSwapQuotes); - - return { - crossSwapQuotes: { - ...crossSwapQuotes, - // fees, - }, integratorId, skipOriginTxEstimation, isInputNative, + isOutputNative, + refundAddress, + recipient, + depositor, + slippageTolerance, }; } @@ -193,10 +176,10 @@ async function calculateSwapFee( ]); const normalizedIn = - parseFloat(formatUnits(expectedAmountIn, tokenIn.decimals)) * + parseFloat(utils.formatUnits(expectedAmountIn, tokenIn.decimals)) * inputTokenPriceBase; const normalizedOut = - parseFloat(formatUnits(expectedAmountOut, tokenOut.decimals)) * + parseFloat(utils.formatUnits(expectedAmountOut, tokenOut.decimals)) * outputTokenPriceBase; return { [baseCurrency]: normalizedIn - normalizedOut, @@ -216,7 +199,7 @@ async function calculateBridgeFee( ); const normalizedFee = parseFloat( - formatUnits(suggestedFees.totalRelayFee.total, inputToken.decimals) + utils.formatUnits(suggestedFees.totalRelayFee.total, inputToken.decimals) ) * inputTokenPriceBase; return { diff --git a/api/swap/allowance.ts b/api/swap/allowance.ts index 45b9e556b..488216f8e 100644 --- a/api/swap/allowance.ts +++ b/api/swap/allowance.ts @@ -1,189 +1,3 @@ -import { VercelResponse } from "@vercel/node"; -import { BigNumber, constants } from "ethers"; - -import { TypedVercelRequest } from "../_types"; -import { - getLogger, - getProvider, - handleErrorCondition, - latestGasPriceCache, - Profiler, -} from "../_utils"; -import { buildCrossSwapTxForAllowanceHolder } from "../_dexes/cross-swap"; -import { - handleBaseSwapQueryParams, - BaseSwapQueryParams, - getApprovalTxns, -} from "./_utils"; -import { getBalanceAndAllowance } from "../_erc20"; - -const handler = async ( - request: TypedVercelRequest, - response: VercelResponse -) => { - const logger = getLogger(); - logger.debug({ - at: "Swap/allowance", - message: "Query data", - query: request.query, - }); - try { - const profiler = new Profiler({ - at: "swap/allowance", - logger: console, - }); - const mark = profiler.start("e2e endpoint runtime"); - const { - crossSwapQuotes, - integratorId, - skipOriginTxEstimation, - isInputNative, - } = await handleBaseSwapQueryParams(request.query); - - const crossSwapTx = await buildCrossSwapTxForAllowanceHolder( - crossSwapQuotes, - integratorId - ); - - const { originSwapQuote, bridgeQuote, destinationSwapQuote, crossSwap } = - crossSwapQuotes; - - const originChainId = crossSwap.inputToken.chainId; - const inputTokenAddress = isInputNative - ? constants.AddressZero - : crossSwap.inputToken.address; - const inputAmount = - originSwapQuote?.maximumAmountIn || bridgeQuote.inputAmount; - - const { allowance, balance } = await getBalanceAndAllowance({ - chainId: originChainId, - tokenAddress: inputTokenAddress, - owner: crossSwap.depositor, - spender: crossSwapTx.to, - }); - - const isSwapTxEstimationPossible = - !skipOriginTxEstimation && - allowance.gte(inputAmount) && - balance.gte(inputAmount); - - let originTxGas: BigNumber | undefined; - let originTxGasPrice: BigNumber | undefined; - if (isSwapTxEstimationPossible) { - const provider = getProvider(originChainId); - [originTxGas, originTxGasPrice] = await Promise.all([ - provider.estimateGas({ - ...crossSwapTx, - from: crossSwap.depositor, - }), - latestGasPriceCache(originChainId).get(), - ]); - } else { - originTxGasPrice = await latestGasPriceCache(originChainId).get(); - } - - let approvalTxns: - | { - chainId: number; - to: string; - data: string; - }[] - | undefined; - // @TODO: Allow for just enough approval amount to be set. - const approvalAmount = constants.MaxUint256; - if (allowance.lt(inputAmount)) { - approvalTxns = getApprovalTxns({ - token: crossSwap.inputToken, - spender: crossSwapTx.to, - amount: approvalAmount, - }); - } - - const refundToken = crossSwap.refundOnOrigin - ? bridgeQuote.inputToken - : bridgeQuote.outputToken; - - const responseJson = { - // fees: crossSwapQuotes.fees, - checks: { - allowance: { - token: inputTokenAddress, - spender: crossSwapTx.to, - actual: allowance.toString(), - expected: inputAmount.toString(), - }, - balance: { - token: inputTokenAddress, - actual: balance.toString(), - expected: inputAmount.toString(), - }, - }, - approvalTxns, - quotes: { - originSwap: originSwapQuote - ? { - tokenIn: originSwapQuote.tokenIn, - tokenOut: originSwapQuote.tokenOut, - inputAmount: originSwapQuote.expectedAmountIn.toString(), - outputAmount: originSwapQuote.expectedAmountOut.toString(), - minOutputAmount: originSwapQuote.minAmountOut.toString(), - maxInputAmount: originSwapQuote.maximumAmountIn.toString(), - } - : undefined, - bridge: { - inputAmount: bridgeQuote.inputAmount.toString(), - outputAmount: bridgeQuote.outputAmount.toString(), - tokenIn: bridgeQuote.inputToken, - tokenOut: bridgeQuote.outputToken, - }, - destinationSwap: destinationSwapQuote - ? { - tokenIn: destinationSwapQuote.tokenIn, - tokenOut: destinationSwapQuote.tokenOut, - inputAmount: destinationSwapQuote.expectedAmountIn.toString(), - maxInputAmount: destinationSwapQuote.maximumAmountIn.toString(), - outputAmount: destinationSwapQuote.expectedAmountOut.toString(), - minOutputAmount: destinationSwapQuote.minAmountOut.toString(), - } - : undefined, - }, - swapTx: { - simulationSuccess: !!originTxGas, - chainId: originChainId, - to: crossSwapTx.to, - data: crossSwapTx.data, - value: crossSwapTx.value?.toString(), - gas: originTxGas?.toString(), - gasPrice: originTxGasPrice?.toString(), - }, - refundToken: - refundToken.symbol === "ETH" - ? { - ...refundToken, - symbol: "WETH", - } - : refundToken, - inputAmount: - originSwapQuote?.expectedAmountIn.toString() ?? - bridgeQuote.inputAmount.toString(), - expectedOutputAmount: - destinationSwapQuote?.expectedAmountOut.toString() ?? - bridgeQuote.outputAmount.toString(), - minOutputAmount: - destinationSwapQuote?.minAmountOut.toString() ?? - bridgeQuote.outputAmount.toString(), - expectedFillTime: bridgeQuote.suggestedFees.estimatedFillTimeSec, - }; - mark.stop(); - logger.debug({ - at: "Swap/allowance", - message: "Response data", - responseJson, - }); - response.status(200).json(responseJson); - } catch (error: unknown) { - return handleErrorCondition("swap/allowance", response, logger, error); - } -}; - -export default handler; +// Maps to /swap/approval +import { default as approvalHandler } from "./approval"; +export default approvalHandler; diff --git a/api/swap/approval/_utils.ts b/api/swap/approval/_utils.ts new file mode 100644 index 000000000..da8b19800 --- /dev/null +++ b/api/swap/approval/_utils.ts @@ -0,0 +1,150 @@ +import { PopulatedTransaction } from "ethers"; + +import { CrossSwapQuotes } from "../../_dexes/types"; +import { tagIntegratorId } from "../../_integrator-id"; +import { getSpokePool } from "../../_utils"; +import { + getSpokePoolPeriphery, + getSpokePoolPeripheryProxy, + TransferType, +} from "../../_spoke-pool-periphery"; +import { extractDepositDataStruct } from "../../_dexes/utils"; +import { getUniversalSwapAndBridge } from "../../_swap-and-bridge"; + +export async function buildCrossSwapTxForAllowanceHolder( + crossSwapQuotes: CrossSwapQuotes, + integratorId?: string +) { + const { originSwapQuote, crossSwap, contracts } = crossSwapQuotes; + const { originSwapEntryPoint, originRouter, depositEntryPoint } = contracts; + const originChainId = crossSwap.inputToken.chainId; + + const deposit = await extractDepositDataStruct(crossSwapQuotes); + + let tx: PopulatedTransaction; + let toAddress: string; + + // Build origin swap tx + if (originSwapQuote) { + if (!originSwapEntryPoint || !originRouter) { + throw new Error( + `'originSwapEntryPoint' and 'originRouter' need to be defined for origin swap quotes` + ); + } + if (originSwapEntryPoint.name === "SpokePoolPeripheryProxy") { + const spokePoolPeripheryProxy = getSpokePoolPeripheryProxy( + originSwapEntryPoint.address, + originChainId + ); + tx = await spokePoolPeripheryProxy.populateTransaction.swapAndBridge( + { + submissionFees: { + amount: 0, + recipient: crossSwap.depositor, + }, + depositData: deposit, + swapToken: originSwapQuote.tokenIn.address, + exchange: originRouter.address, + transferType: TransferType.Approval, + swapTokenAmount: originSwapQuote.maximumAmountIn, + minExpectedInputTokenAmount: originSwapQuote.minAmountOut, + routerCalldata: originSwapQuote.swapTx.data, + } + // TODO: Add payable modifier to SpokePoolPeripheryProxy swapAndBridge function + // { + // value: crossSwap.isInputNative ? originSwapQuote.maximumAmountIn : 0, + // } + ); + toAddress = spokePoolPeripheryProxy.address; + } else if (originSwapEntryPoint.name === "UniversalSwapAndBridge") { + const universalSwapAndBridge = getUniversalSwapAndBridge( + originSwapEntryPoint.dex, + originChainId + ); + tx = await universalSwapAndBridge.populateTransaction.swapAndBridge( + originSwapQuote.tokenIn.address, + originSwapQuote.tokenOut.address, + originSwapQuote.swapTx.data, + originSwapQuote.maximumAmountIn, + originSwapQuote.minAmountOut, + { + ...deposit, + // Typo in the contract + destinationChainid: deposit.destinationChainId, + } + ); + toAddress = universalSwapAndBridge.address; + } else { + throw new Error( + `Could not build 'swapAndBridge' tx for unknown entry point contract` + ); + } + } + // Build deposit tx + else { + if (!depositEntryPoint) { + throw new Error( + `'depositEntryPoint' needs to be defined for bridge quotes` + ); + } + + if (depositEntryPoint.name === "SpokePoolPeriphery") { + const spokePoolPeriphery = getSpokePoolPeriphery( + depositEntryPoint.address, + originChainId + ); + tx = await spokePoolPeriphery.populateTransaction.deposit( + deposit.recipient, + deposit.inputToken, + // deposit.outputToken, // TODO: allow for output token in periphery contract + deposit.inputAmount, + deposit.outputAmount, + deposit.destinationChainId, + deposit.exclusiveRelayer, + deposit.quoteTimestamp, + deposit.fillDeadline, + deposit.exclusivityDeadline, + deposit.message, + { + value: crossSwapQuotes.crossSwap.isInputNative + ? deposit.inputAmount + : 0, + } + ); + toAddress = spokePoolPeriphery.address; + } else if (depositEntryPoint.name === "SpokePool") { + const spokePool = getSpokePool(originChainId); + 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, + { + value: crossSwapQuotes.crossSwap.isInputNative + ? deposit.inputAmount + : 0, + } + ); + toAddress = spokePool.address; + } else { + throw new Error( + `Could not build 'deposit' tx for unknown entry point contract` + ); + } + } + + return { + from: crossSwapQuotes.crossSwap.depositor, + to: toAddress, + data: integratorId ? tagIntegratorId(integratorId, tx.data!) : tx.data, + value: tx.value, + }; +} diff --git a/api/swap/approval/index.ts b/api/swap/approval/index.ts new file mode 100644 index 000000000..72f0232be --- /dev/null +++ b/api/swap/approval/index.ts @@ -0,0 +1,224 @@ +import { VercelResponse } from "@vercel/node"; +import { BigNumber, constants } from "ethers"; + +import { TypedVercelRequest } from "../../_types"; +import { + getLogger, + getProvider, + handleErrorCondition, + latestGasPriceCache, + Profiler, +} from "../../_utils"; +import { buildCrossSwapTxForAllowanceHolder } from "./_utils"; +import { + handleBaseSwapQueryParams, + BaseSwapQueryParams, + getApprovalTxns, +} from "../_utils"; +import { getBalanceAndAllowance } from "../../_erc20"; +import { getCrossSwapQuotes } from "../../_dexes/cross-swap-service"; +import { getSwapRouter02Strategy } from "../../_dexes/uniswap/swap-router-02"; +import { QuoteFetchStrategies } from "../../_dexes/utils"; + +// For approval-based flows, we use the `UniversalSwapAndBridge` strategy with Uniswap V3's `SwapRouter02` +const quoteFetchStrategies: QuoteFetchStrategies = { + default: getSwapRouter02Strategy("UniversalSwapAndBridge"), +}; + +const handler = async ( + request: TypedVercelRequest, + response: VercelResponse +) => { + const logger = getLogger(); + logger.debug({ + at: "Swap/approval", + message: "Query data", + query: request.query, + }); + try { + const profiler = new Profiler({ + at: "swap/approval", + logger: console, + }); + const mark = profiler.start("e2e endpoint runtime"); + + const { + integratorId, + skipOriginTxEstimation, + isInputNative, + isOutputNative, + inputToken, + outputToken, + amount, + amountType, + refundOnOrigin, + refundAddress, + recipient, + depositor, + slippageTolerance, + } = await handleBaseSwapQueryParams(request.query); + + const crossSwapQuotes = await getCrossSwapQuotes( + { + amount, + inputToken, + outputToken, + depositor, + recipient: recipient || depositor, + slippageTolerance: Number(slippageTolerance), + type: amountType, + refundOnOrigin, + refundAddress, + isInputNative, + isOutputNative, + }, + quoteFetchStrategies + ); + + const crossSwapTx = await buildCrossSwapTxForAllowanceHolder( + crossSwapQuotes, + integratorId + ); + + const { originSwapQuote, bridgeQuote, destinationSwapQuote, crossSwap } = + crossSwapQuotes; + + const originChainId = crossSwap.inputToken.chainId; + const inputTokenAddress = isInputNative + ? constants.AddressZero + : crossSwap.inputToken.address; + const inputAmount = + originSwapQuote?.maximumAmountIn || bridgeQuote.inputAmount; + + const { allowance, balance } = await getBalanceAndAllowance({ + chainId: originChainId, + tokenAddress: inputTokenAddress, + owner: crossSwap.depositor, + spender: crossSwapTx.to, + }); + + const isSwapTxEstimationPossible = + !skipOriginTxEstimation && + allowance.gte(inputAmount) && + balance.gte(inputAmount); + + let originTxGas: BigNumber | undefined; + let originTxGasPrice: BigNumber | undefined; + if (isSwapTxEstimationPossible) { + const provider = getProvider(originChainId); + [originTxGas, originTxGasPrice] = await Promise.all([ + provider.estimateGas({ + ...crossSwapTx, + from: crossSwap.depositor, + }), + latestGasPriceCache(originChainId).get(), + ]); + } else { + originTxGasPrice = await latestGasPriceCache(originChainId).get(); + } + + let approvalTxns: + | { + chainId: number; + to: string; + data: string; + }[] + | undefined; + // @TODO: Allow for just enough approval amount to be set. + const approvalAmount = constants.MaxUint256; + if (allowance.lt(inputAmount)) { + approvalTxns = getApprovalTxns({ + token: crossSwap.inputToken, + spender: crossSwapTx.to, + amount: approvalAmount, + }); + } + + const refundToken = crossSwap.refundOnOrigin + ? bridgeQuote.inputToken + : bridgeQuote.outputToken; + + const responseJson = { + // fees: crossSwapQuotes.fees, + checks: { + allowance: { + token: inputTokenAddress, + spender: crossSwapTx.to, + actual: allowance.toString(), + expected: inputAmount.toString(), + }, + balance: { + token: inputTokenAddress, + actual: balance.toString(), + expected: inputAmount.toString(), + }, + }, + approvalTxns, + steps: { + originSwap: originSwapQuote + ? { + tokenIn: originSwapQuote.tokenIn, + tokenOut: originSwapQuote.tokenOut, + inputAmount: originSwapQuote.expectedAmountIn.toString(), + outputAmount: originSwapQuote.expectedAmountOut.toString(), + minOutputAmount: originSwapQuote.minAmountOut.toString(), + maxInputAmount: originSwapQuote.maximumAmountIn.toString(), + } + : undefined, + bridge: { + inputAmount: bridgeQuote.inputAmount.toString(), + outputAmount: bridgeQuote.outputAmount.toString(), + tokenIn: bridgeQuote.inputToken, + tokenOut: bridgeQuote.outputToken, + }, + destinationSwap: destinationSwapQuote + ? { + tokenIn: destinationSwapQuote.tokenIn, + tokenOut: destinationSwapQuote.tokenOut, + inputAmount: destinationSwapQuote.expectedAmountIn.toString(), + maxInputAmount: destinationSwapQuote.maximumAmountIn.toString(), + outputAmount: destinationSwapQuote.expectedAmountOut.toString(), + minOutputAmount: destinationSwapQuote.minAmountOut.toString(), + } + : undefined, + }, + swapTx: { + simulationSuccess: !!originTxGas, + chainId: originChainId, + to: crossSwapTx.to, + data: crossSwapTx.data, + value: crossSwapTx.value?.toString(), + gas: originTxGas?.toString(), + gasPrice: originTxGasPrice?.toString(), + }, + refundToken: + refundToken.symbol === "ETH" + ? { + ...refundToken, + symbol: "WETH", + } + : refundToken, + inputAmount: + originSwapQuote?.expectedAmountIn.toString() ?? + bridgeQuote.inputAmount.toString(), + expectedOutputAmount: + destinationSwapQuote?.expectedAmountOut.toString() ?? + bridgeQuote.outputAmount.toString(), + minOutputAmount: + destinationSwapQuote?.minAmountOut.toString() ?? + bridgeQuote.outputAmount.toString(), + expectedFillTime: bridgeQuote.suggestedFees.estimatedFillTimeSec, + }; + mark.stop(); + logger.debug({ + at: "Swap/approval", + message: "Response data", + responseJson, + }); + response.status(200).json(responseJson); + } catch (error: unknown) { + return handleErrorCondition("swap/approval", response, logger, error); + } +}; + +export default handler; diff --git a/api/swap/permit/_utils.ts b/api/swap/permit/_utils.ts new file mode 100644 index 000000000..49a231945 --- /dev/null +++ b/api/swap/permit/_utils.ts @@ -0,0 +1,127 @@ +import { + CrossSwapQuotes, + DepositEntryPointContract, + OriginSwapEntryPointContract, +} from "../../_dexes/types"; +import { getPermitTypedData } from "../../_permit"; +import { + getDepositTypedData, + getSwapAndDepositTypedData, + TransferType, +} from "../../_spoke-pool-periphery"; +import { extractDepositDataStruct } from "../../_dexes/utils"; +import { BigNumber } from "ethers"; + +export async function buildPermitTxPayload( + crossSwapQuotes: CrossSwapQuotes, + permitDeadline: number +) { + const { originSwapQuote, bridgeQuote, crossSwap, contracts } = + crossSwapQuotes; + const originChainId = crossSwap.inputToken.chainId; + const { originSwapEntryPoint, depositEntryPoint, originRouter } = contracts; + + const baseDepositData = await extractDepositDataStruct(crossSwapQuotes); + + let entryPointContract: + | DepositEntryPointContract + | OriginSwapEntryPointContract; + let getDepositTypedDataPromise: + | ReturnType + | ReturnType; + let methodName: string; + + if (originSwapQuote) { + if (!originSwapEntryPoint) { + throw new Error( + `'originSwapEntryPoint' needs to be defined for origin swap quotes` + ); + } + // Only SpokePoolPeriphery supports permit + if (originSwapEntryPoint.name !== "SpokePoolPeriphery") { + throw new Error( + `Permit is not supported for origin swap entry point contract '${originSwapEntryPoint.name}'` + ); + } + + if (!originRouter) { + throw new Error( + `'originRouter' needs to be defined for origin swap quotes` + ); + } + + entryPointContract = originSwapEntryPoint; + getDepositTypedDataPromise = getSwapAndDepositTypedData({ + swapAndDepositData: { + // TODO: Make this dynamic + submissionFees: { + amount: BigNumber.from(0), + recipient: crossSwapQuotes.crossSwap.depositor, + }, + depositData: baseDepositData, + swapToken: originSwapQuote.tokenIn.address, + swapTokenAmount: originSwapQuote.maximumAmountIn, + minExpectedInputTokenAmount: originSwapQuote.minAmountOut, + routerCalldata: originSwapQuote.swapTx.data, + exchange: originRouter.address, + transferType: + originRouter.name === "UniswapV3UniversalRouter" + ? TransferType.Transfer + : TransferType.Approval, + }, + chainId: originChainId, + }); + methodName = "swapAndBridgeWithPermit"; + } else { + if (!depositEntryPoint) { + throw new Error( + `'depositEntryPoint' needs to be defined for bridge quotes` + ); + } + + if (depositEntryPoint.name !== "SpokePoolPeriphery") { + throw new Error( + `Permit is not supported for deposit entry point contract '${depositEntryPoint.name}'` + ); + } + + entryPointContract = depositEntryPoint; + getDepositTypedDataPromise = getDepositTypedData({ + depositData: { + // TODO: Make this dynamic + submissionFees: { + amount: BigNumber.from(0), + recipient: crossSwap.depositor, + }, + baseDepositData, + inputAmount: BigNumber.from(bridgeQuote.inputAmount), + }, + chainId: originChainId, + }); + methodName = "depositWithPermit"; + } + + const [permitTypedData, depositTypedData] = await Promise.all([ + getPermitTypedData({ + tokenAddress: + originSwapQuote?.tokenIn.address || bridgeQuote.inputToken.address, + chainId: originChainId, + ownerAddress: crossSwap.depositor, + spenderAddress: entryPointContract.address, + value: originSwapQuote?.maximumAmountIn || bridgeQuote.inputAmount, + deadline: permitDeadline, + }), + getDepositTypedDataPromise, + ]); + return { + eip712: { + permit: permitTypedData.eip712, + deposit: depositTypedData.eip712, + }, + swapTx: { + chainId: originChainId, + to: entryPointContract.address, + methodName, + }, + }; +} diff --git a/api/swap/permit.ts b/api/swap/permit/index.ts similarity index 50% rename from api/swap/permit.ts rename to api/swap/permit/index.ts index 370bfc795..bb141c6d0 100644 --- a/api/swap/permit.ts +++ b/api/swap/permit/index.ts @@ -1,10 +1,14 @@ import { VercelResponse } from "@vercel/node"; import { assert, Infer, optional, type } from "superstruct"; -import { TypedVercelRequest } from "../_types"; -import { getLogger, handleErrorCondition, positiveIntStr } from "../_utils"; -import { getCrossSwapTxForPermit } from "../_dexes/cross-swap"; -import { handleBaseSwapQueryParams, BaseSwapQueryParams } from "./_utils"; +import { TypedVercelRequest } from "../../_types"; +import { getLogger, handleErrorCondition, positiveIntStr } from "../../_utils"; +import { getCrossSwapQuotes } from "../../_dexes/cross-swap-service"; +import { handleBaseSwapQueryParams, BaseSwapQueryParams } from "../_utils"; +import { getSwapRouter02Strategy } from "../../_dexes/uniswap/swap-router-02"; +import { InvalidParamError } from "../../_errors"; +import { buildPermitTxPayload } from "./_utils"; +import { QuoteFetchStrategies } from "../../_dexes/utils"; export const PermitSwapQueryParamsSchema = type({ permitDeadline: optional(positiveIntStr()), @@ -15,6 +19,11 @@ export type PermitSwapQueryParams = Infer; const DEFAULT_PERMIT_DEADLINE = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365; // 1 year +// For permit-based flows, we have to use the `SpokePoolPeriphery` as an entry point +const quoteFetchStrategies: QuoteFetchStrategies = { + default: getSwapRouter02Strategy("SpokePoolPeriphery"), +}; + const handler = async ( request: TypedVercelRequest, response: VercelResponse @@ -36,11 +45,47 @@ const handler = async ( ); const permitDeadline = Number(_permitDeadline ?? DEFAULT_PERMIT_DEADLINE); + if (permitDeadline < Math.floor(Date.now() / 1000)) { + throw new InvalidParamError({ + message: + "Permit deadline must be a UNIX timestamp (seconds) in the future", + param: "permitDeadline", + }); + } + // `/swap` specific params validation + quote generation - const { crossSwapQuotes } = await handleBaseSwapQueryParams(restQuery); + const { + isInputNative, + isOutputNative, + inputToken, + outputToken, + amount, + amountType, + refundOnOrigin, + refundAddress, + recipient, + depositor, + slippageTolerance, + } = await handleBaseSwapQueryParams(restQuery); + const crossSwapQuotes = await getCrossSwapQuotes( + { + amount, + inputToken, + outputToken, + depositor, + recipient: recipient || depositor, + slippageTolerance: Number(slippageTolerance), + type: amountType, + refundOnOrigin, + refundAddress, + isInputNative, + isOutputNative, + }, + quoteFetchStrategies + ); // Build tx for permit - const crossSwapTxForPermit = await getCrossSwapTxForPermit( + const crossSwapTxForPermit = await buildPermitTxPayload( crossSwapQuotes, permitDeadline ); diff --git a/scripts/generate-routes.ts b/scripts/generate-routes.ts index 125f48956..d60be8cec 100644 --- a/scripts/generate-routes.ts +++ b/scripts/generate-routes.ts @@ -126,21 +126,26 @@ const enabledRoutes = { }, }, spokePoolPeripheryAddresses: { - "uniswap-swapRouter02": { - [CHAIN_IDs.POLYGON]: "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - [CHAIN_IDs.OPTIMISM]: "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - [CHAIN_IDs.ARBITRUM]: "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - [CHAIN_IDs.BASE]: "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - [CHAIN_IDs.ZK_SYNC]: "0xB007dFe9A6b70e1AB5BD5E97C22e47C5e0c0B8D8", - [CHAIN_IDs.WORLD_CHAIN]: "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - [CHAIN_IDs.BLAST]: "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - [CHAIN_IDs.ZORA]: "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - [CHAIN_IDs.MAINNET]: "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - }, - "uniswap-universalRouter": { - [CHAIN_IDs.OPTIMISM]: "0xaED9bBFdCC63219d77D54F8427aeb84C2Be46c5f", - [CHAIN_IDs.ARBITRUM]: "0xaED9bBFdCC63219d77D54F8427aeb84C2Be46c5f", - }, + [CHAIN_IDs.ARBITRUM]: "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + [CHAIN_IDs.BASE]: "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + [CHAIN_IDs.BLAST]: "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + [CHAIN_IDs.MAINNET]: "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + [CHAIN_IDs.OPTIMISM]: "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + [CHAIN_IDs.POLYGON]: "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + [CHAIN_IDs.WORLD_CHAIN]: "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + [CHAIN_IDs.ZK_SYNC]: "0xB007dFe9A6b70e1AB5BD5E97C22e47C5e0c0B8D8", + [CHAIN_IDs.ZORA]: "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + }, + spokePoolPeripheryProxyAddresses: { + [CHAIN_IDs.ARBITRUM]: "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + [CHAIN_IDs.BASE]: "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + [CHAIN_IDs.BLAST]: "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + [CHAIN_IDs.MAINNET]: "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + [CHAIN_IDs.OPTIMISM]: "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + [CHAIN_IDs.POLYGON]: "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + [CHAIN_IDs.WORLD_CHAIN]: "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + [CHAIN_IDs.ZK_SYNC]: "0xB007dFe9A6b70e1AB5BD5E97C22e47C5e0c0B8D8", + [CHAIN_IDs.ZORA]: "0xAa074de443aee6725EA5aC45FF2add1dC8366485", }, routes: transformChainConfigs(enabledMainnetChainConfigs), }, @@ -172,9 +177,8 @@ const enabledRoutes = { universalSwapAndBridgeAddresses: { uniswap: {}, }, - spokePoolPeripheryAddresses: { - "uniswap-universalRouter": {}, - }, + spokePoolPeripheryAddresses: {}, + spokePoolPeripheryProxyAddresses: {}, routes: transformChainConfigs(enabledSepoliaChainConfigs), }, } as const; @@ -406,11 +410,11 @@ async function generateRoutes(hubPoolChainId = 1) { Record > ), - spokePoolPeripheryAddresses: checksumAddressesOfNestedMap( - config.spokePoolPeripheryAddresses as Record< - string, - Record - > + spokePoolPeripheryAddresses: checksumAddressOfMap( + config.spokePoolPeripheryAddresses as Record + ), + spokePoolPeripheryProxyAddresses: checksumAddressOfMap( + config.spokePoolPeripheryProxyAddresses as Record ), routes: config.routes.flatMap((route) => transformBridgeRoute(route, config.hubPoolChain) @@ -665,19 +669,20 @@ function getBridgedUsdcSymbol(chainId: number) { } } +function checksumAddressOfMap(map: Record) { + return Object.entries(map).reduce( + (acc, [key, value]) => ({ ...acc, [key]: utils.getAddress(value) }), + {} + ); +} + function checksumAddressesOfNestedMap( nestedMap: Record> ) { return Object.entries(nestedMap).reduce( (acc, [key, value]) => ({ ...acc, - [key]: Object.entries(value).reduce( - (acc, [chainId, address]) => ({ - ...acc, - [chainId]: utils.getAddress(address as string), - }), - {} - ), + [key]: checksumAddressOfMap(value), }), {} ); diff --git a/scripts/tests/_swap-utils.ts b/scripts/tests/_swap-utils.ts index 17fa146a8..48d0fa14f 100644 --- a/scripts/tests/_swap-utils.ts +++ b/scripts/tests/_swap-utils.ts @@ -170,19 +170,16 @@ export function filterTestCases( return filteredTestCases; } -export async function swap() { +export async function swap(slug: "approval" | "permit") { const filterString = process.argv[2]; const testCases = [...MIN_OUTPUT_CASES, ...EXACT_OUTPUT_CASES]; const filteredTestCases = filterTestCases(testCases, filterString); for (const testCase of filteredTestCases) { console.log("\nTest case:", testCase.labels.join(" ")); console.log("Params:", testCase.params); - const response = await axios.get( - `${SWAP_API_BASE_URL}/api/swap/allowance`, - { - params: testCase.params, - } - ); + const response = await axios.get(`${SWAP_API_BASE_URL}/api/swap/${slug}`, { + params: testCase.params, + }); console.log(response.data); if (process.env.DEV_WALLET_PK) { diff --git a/scripts/tests/swap-allowance.ts b/scripts/tests/swap-allowance.ts index b4886ae26..05162cba1 100644 --- a/scripts/tests/swap-allowance.ts +++ b/scripts/tests/swap-allowance.ts @@ -2,7 +2,7 @@ import { swap } from "./_swap-utils"; async function swapWithAllowance() { console.log("Swapping with allowance..."); - await swap(); + await swap("approval"); } swapWithAllowance() diff --git a/scripts/tests/swap-permit.ts b/scripts/tests/swap-permit.ts index e448c3f78..29c8296eb 100644 --- a/scripts/tests/swap-permit.ts +++ b/scripts/tests/swap-permit.ts @@ -2,7 +2,7 @@ import { swap } from "./_swap-utils"; async function swapWithPermit() { console.log("Swapping with permit..."); - await swap(); + await swap("permit"); } swapWithPermit() diff --git a/src/data/routes_11155111_0x14224e63716afAcE30C9a417E0542281869f7d9e.json b/src/data/routes_11155111_0x14224e63716afAcE30C9a417E0542281869f7d9e.json index c70c802f3..0a883bab8 100644 --- a/src/data/routes_11155111_0x14224e63716afAcE30C9a417E0542281869f7d9e.json +++ b/src/data/routes_11155111_0x14224e63716afAcE30C9a417E0542281869f7d9e.json @@ -16,9 +16,8 @@ "universalSwapAndBridgeAddresses": { "uniswap": {} }, - "spokePoolPeripheryAddresses": { - "uniswap-universalRouter": {} - }, + "spokePoolPeripheryAddresses": {}, + "spokePoolPeripheryProxyAddresses": {}, "routes": [ { "fromChain": 11155111, diff --git a/src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json b/src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json index ed6a01693..a4e5abbfc 100644 --- a/src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json +++ b/src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json @@ -39,21 +39,26 @@ } }, "spokePoolPeripheryAddresses": { - "uniswap-swapRouter02": { - "1": "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - "10": "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - "137": "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - "324": "0xB007dFe9A6b70e1AB5BD5E97C22e47C5e0c0B8D8", - "480": "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - "8453": "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - "42161": "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - "81457": "0x8EB5FF2e23FD7789e59989aDe055A398800E394e", - "7777777": "0x8EB5FF2e23FD7789e59989aDe055A398800E394e" - }, - "uniswap-universalRouter": { - "10": "0xaED9bBFdCC63219d77D54F8427aeb84C2Be46c5f", - "42161": "0xaED9bBFdCC63219d77D54F8427aeb84C2Be46c5f" - } + "1": "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + "10": "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + "137": "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + "324": "0xB007dFe9A6b70e1AB5BD5E97C22e47C5e0c0B8D8", + "480": "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + "8453": "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + "42161": "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + "81457": "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a", + "7777777": "0xF5E3419d9107dE8da3E16ccb5e305686FAC2Cc1a" + }, + "spokePoolPeripheryProxyAddresses": { + "1": "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + "10": "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + "137": "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + "324": "0xB007dFe9A6b70e1AB5BD5E97C22e47C5e0c0B8D8", + "480": "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + "8453": "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + "42161": "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + "81457": "0xAa074de443aee6725EA5aC45FF2add1dC8366485", + "7777777": "0xAa074de443aee6725EA5aC45FF2add1dC8366485" }, "routes": [ { From b487bac58c11f3dff88a6147a8f70c6dc2fa5a19 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Fri, 20 Dec 2024 13:04:22 +0100 Subject: [PATCH 06/20] fixup --- api/swap-quote.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/swap-quote.ts b/api/swap-quote.ts index cfca8f8c0..6bdd6386f 100644 --- a/api/swap-quote.ts +++ b/api/swap-quote.ts @@ -16,7 +16,7 @@ import { import { getUniswapQuoteWithSwapQuoter } from "./_dexes/uniswap/swap-quoter"; import { get1inchQuoteForOriginSwapExactInput } from "./_dexes/1inch"; import { InvalidParamError } from "./_errors"; -import { AMOUNT_TYPE } from "./_dexes/cross-swap-service"; +import { AMOUNT_TYPE } from "./_dexes/utils"; const SwapQuoteQueryParamsSchema = type({ swapToken: validAddress(), From 4ff0ce4fd08ccdb363a25ea3280026c74b0e8ae1 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Fri, 20 Dec 2024 16:11:18 +0200 Subject: [PATCH 07/20] support transfer with auth --- api/_abis.ts | 30 ++++++++ api/_transfer-with-auth.ts | 137 +++++++++++++++++++++++++++++++++++ api/swap/auth/_utils.ts | 133 ++++++++++++++++++++++++++++++++++ api/swap/auth/index.ts | 112 ++++++++++++++++++++++++++++ scripts/tests/_swap-utils.ts | 2 +- scripts/tests/swap-auth.ts | 15 ++++ 6 files changed, 428 insertions(+), 1 deletion(-) create mode 100644 api/_transfer-with-auth.ts create mode 100644 api/swap/auth/_utils.ts create mode 100644 api/swap/auth/index.ts create mode 100644 scripts/tests/swap-auth.ts diff --git a/api/_abis.ts b/api/_abis.ts index 3dd4fc41f..8fe7a37ff 100644 --- a/api/_abis.ts +++ b/api/_abis.ts @@ -126,3 +126,33 @@ export const ERC20_PERMIT_ABI = [ type: "function", }, ]; + +export const ERC_TRANSFER_WITH_AUTH_ABI = [ + { + inputs: [], + stateMutability: "view", + type: "function", + name: "name", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + }, + { + inputs: [], + name: "version", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "DOMAIN_SEPARATOR", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, +]; diff --git a/api/_transfer-with-auth.ts b/api/_transfer-with-auth.ts new file mode 100644 index 000000000..27c39d972 --- /dev/null +++ b/api/_transfer-with-auth.ts @@ -0,0 +1,137 @@ +import { BigNumberish, ethers } from "ethers"; +import { getProvider } from "./_utils"; +import { ERC_TRANSFER_WITH_AUTH_ABI } from "./_abis"; +import { utils } from "ethers"; + +export function hashDomainSeparator(params: { + name: string; + version: string | number; + chainId: number; + verifyingContract: string; +}): string { + return utils.keccak256( + utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "bytes32", "uint256", "address"], + [ + utils.id( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ), + utils.id(params.name), + utils.id(params.version.toString()), + params.chainId, + params.verifyingContract, + ] + ) + ); +} + +export async function getTransferWithAuthTypedData(params: { + tokenAddress: string; + chainId: number; + ownerAddress: string; + spenderAddress: string; + value: BigNumberish; + validBefore: number; + nonce: string; + validAfter?: number; + eip712DomainVersion?: number; +}) { + const provider = getProvider(params.chainId); + + const erc20Permit = new ethers.Contract( + params.tokenAddress, + ERC_TRANSFER_WITH_AUTH_ABI, + provider + ); + + const [nameResult, versionFromContractResult, domainSeparatorResult] = + await Promise.allSettled([ + erc20Permit.name(), + erc20Permit.version(), + erc20Permit.DOMAIN_SEPARATOR(), + ]); + + if ( + nameResult.status === "rejected" || + domainSeparatorResult.status === "rejected" + ) { + const error = + nameResult.status === "rejected" + ? nameResult.reason + : domainSeparatorResult.status === "rejected" + ? domainSeparatorResult.reason + : new Error("Unknown error"); + throw new Error( + `Contract ${params.tokenAddress} does not support transfer with authorization`, + { + cause: error, + } + ); + } + + const name = nameResult.value; + const versionFromContract = + versionFromContractResult.status === "fulfilled" + ? versionFromContractResult.value + : undefined; + const domainSeparator = domainSeparatorResult.value; + + const eip712DomainVersion = [1, 2, "1", "2"].includes(versionFromContract) + ? Number(versionFromContract) + : params.eip712DomainVersion || 1; + + const domainSeparatorHash = hashDomainSeparator({ + name, + version: eip712DomainVersion, + chainId: params.chainId, + verifyingContract: params.tokenAddress, + }); + + if (domainSeparator !== domainSeparatorHash) { + throw new Error("EIP712 domain separator mismatch"); + } + + return { + domainSeparator, + eip712: { + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "address" }, + ], + TransferWithAuthorization: [ + { name: "from", type: "address" }, + { name: "to", type: "address" }, + { name: "value", type: "uint256" }, + { name: "validAfter", type: "uint256" }, + { name: "validBefore", type: "uint256" }, + { name: "nonce", type: "bytes32" }, + ], + }, + domain: { + name, + version: eip712DomainVersion.toString(), + chainId: params.chainId, + verifyingContract: params.tokenAddress, + }, + primaryType: "TransferWithAuthorization", + message: { + from: params.ownerAddress, + to: params.spenderAddress, + value: String(params.value), + validAfter: params?.validAfter + ? convertMaybeMillisecondsToSeconds(params.validAfter) + : 0, + validBefore: convertMaybeMillisecondsToSeconds(params.validBefore), + nonce: params.nonce, // non-sequential nonce, random 32 byte hex string + }, + }, + }; +} + +export function convertMaybeMillisecondsToSeconds(timestamp: number): number { + const isMilliseconds = timestamp > 1_000_000_000; // rough approximation + return isMilliseconds ? Math.floor(timestamp / 1000) : Math.floor(timestamp); +} diff --git a/api/swap/auth/_utils.ts b/api/swap/auth/_utils.ts new file mode 100644 index 000000000..bffaca856 --- /dev/null +++ b/api/swap/auth/_utils.ts @@ -0,0 +1,133 @@ +import { + CrossSwapQuotes, + DepositEntryPointContract, + OriginSwapEntryPointContract, +} from "../../_dexes/types"; +import { getTransferWithAuthTypedData } from "../../_transfer-with-auth"; +import { + getDepositTypedData, + getSwapAndDepositTypedData, + TransferType, +} from "../../_spoke-pool-periphery"; +import { extractDepositDataStruct } from "../../_dexes/utils"; +import { BigNumber, utils } from "ethers"; + +export async function buildAuthTxPayload( + crossSwapQuotes: CrossSwapQuotes, + authDeadline: number, // maybe milliseconds + authStart = 0 // maybe milliseconds +) { + const { originSwapQuote, bridgeQuote, crossSwap, contracts } = + crossSwapQuotes; + const originChainId = crossSwap.inputToken.chainId; + const { originSwapEntryPoint, depositEntryPoint, originRouter } = contracts; + + const baseDepositData = await extractDepositDataStruct(crossSwapQuotes); + + let entryPointContract: + | DepositEntryPointContract + | OriginSwapEntryPointContract; + let getDepositTypedDataPromise: + | ReturnType + | ReturnType; + let methodName: string; + + if (originSwapQuote) { + if (!originSwapEntryPoint) { + throw new Error( + `'originSwapEntryPoint' needs to be defined for origin swap quotes` + ); + } + // Only SpokePoolPeriphery supports transfer with auth + if (originSwapEntryPoint.name !== "SpokePoolPeriphery") { + throw new Error( + `Transfer with auth is not supported for origin swap entry point contract '${originSwapEntryPoint.name}'` + ); + } + + if (!originRouter) { + throw new Error( + `'originRouter' needs to be defined for origin swap quotes` + ); + } + + entryPointContract = originSwapEntryPoint; + getDepositTypedDataPromise = getSwapAndDepositTypedData({ + swapAndDepositData: { + // TODO: Make this dynamic + submissionFees: { + amount: BigNumber.from(0), + recipient: crossSwapQuotes.crossSwap.depositor, + }, + depositData: baseDepositData, + swapToken: originSwapQuote.tokenIn.address, + swapTokenAmount: originSwapQuote.maximumAmountIn, + minExpectedInputTokenAmount: originSwapQuote.minAmountOut, + routerCalldata: originSwapQuote.swapTx.data, + exchange: originRouter.address, + transferType: + originRouter.name === "UniswapV3UniversalRouter" + ? TransferType.Transfer + : TransferType.Approval, + }, + chainId: originChainId, + }); + methodName = "swapAndBridgeWithAuthorization"; + } else { + if (!depositEntryPoint) { + throw new Error( + `'depositEntryPoint' needs to be defined for bridge quotes` + ); + } + + if (depositEntryPoint.name !== "SpokePoolPeriphery") { + throw new Error( + `auth is not supported for deposit entry point contract '${depositEntryPoint.name}'` + ); + } + + entryPointContract = depositEntryPoint; + getDepositTypedDataPromise = getDepositTypedData({ + depositData: { + // TODO: Make this dynamic + submissionFees: { + amount: BigNumber.from(0), + recipient: crossSwap.depositor, + }, + baseDepositData, + inputAmount: BigNumber.from(bridgeQuote.inputAmount), + }, + chainId: originChainId, + }); + methodName = "depositWithAuthorization"; + } + + // random non-sequesntial nonce + const nonce = utils.hexlify(utils.randomBytes(32)); + + const [authTypedData, depositTypedData] = await Promise.all([ + getTransferWithAuthTypedData({ + tokenAddress: + originSwapQuote?.tokenIn.address || bridgeQuote.inputToken.address, + chainId: originChainId, + ownerAddress: crossSwap.depositor, + spenderAddress: entryPointContract.address, + value: originSwapQuote?.maximumAmountIn || bridgeQuote.inputAmount, + nonce, + validAfter: authStart, + validBefore: authDeadline, + }), + getDepositTypedDataPromise, + ]); + return { + eip712: { + transferWithAuthorization: authTypedData.eip712, + deposit: depositTypedData.eip712, + }, + swapTx: { + chainId: originChainId, + to: entryPointContract.address, + methodName, + }, + }; +} diff --git a/api/swap/auth/index.ts b/api/swap/auth/index.ts new file mode 100644 index 000000000..6cc971746 --- /dev/null +++ b/api/swap/auth/index.ts @@ -0,0 +1,112 @@ +import { VercelResponse } from "@vercel/node"; +import { assert, Infer, optional, type } from "superstruct"; + +import { TypedVercelRequest } from "../../_types"; +import { getLogger, handleErrorCondition, positiveIntStr } from "../../_utils"; +import { getCrossSwapQuotes } from "../../_dexes/cross-swap-service"; +import { handleBaseSwapQueryParams, BaseSwapQueryParams } from "../_utils"; +import { getSwapRouter02Strategy } from "../../_dexes/uniswap/swap-router-02"; +import { InvalidParamError } from "../../_errors"; +import { QuoteFetchStrategies } from "../../_dexes/utils"; +import { buildAuthTxPayload } from "./_utils"; + +export const authSwapQueryParamsSchema = type({ + authDeadline: optional(positiveIntStr()), +}); + +export type authSwapQueryParams = Infer; + +const DEFAULT_AUTH_DEADLINE = + Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365; // 1 year + +// For auth-based flows, we have to use the `SpokePoolPeriphery` as an entry point +const quoteFetchStrategies: QuoteFetchStrategies = { + default: getSwapRouter02Strategy("SpokePoolPeriphery"), +}; + +const handler = async ( + request: TypedVercelRequest, + response: VercelResponse +) => { + const logger = getLogger(); + logger.debug({ + at: "Swap/auth", + message: "Query data", + query: request.query, + }); + try { + // `/swap/auth` specific params validation + const { + authDeadline: _authDeadline, + authStart: _authStart, + ...restQuery + } = request.query; + assert( + { + authDeadline: _authDeadline, + }, + authSwapQueryParamsSchema + ); + const authDeadline = Number(_authDeadline ?? DEFAULT_AUTH_DEADLINE); + const authStart = Number(_authStart ?? Date.now()); + + if (authDeadline < Math.floor(Date.now() / 1000)) { + throw new InvalidParamError({ + message: + "auth deadline must be a UNIX timestamp (seconds) in the future", + param: "authDeadline", + }); + } + + // `/swap` specific params validation + quote generation + const { + isInputNative, + isOutputNative, + inputToken, + outputToken, + amount, + amountType, + refundOnOrigin, + refundAddress, + recipient, + depositor, + slippageTolerance, + } = await handleBaseSwapQueryParams(restQuery); + + const crossSwapQuotes = await getCrossSwapQuotes( + { + amount, + inputToken, + outputToken, + depositor, + recipient: recipient || depositor, + slippageTolerance: Number(slippageTolerance), + type: amountType, + refundOnOrigin, + refundAddress, + isInputNative, + isOutputNative, + }, + quoteFetchStrategies + ); + // Build tx for auth + const crossSwapTxForAuth = await buildAuthTxPayload( + crossSwapQuotes, + authDeadline, + authStart + ); + + const responseJson = crossSwapTxForAuth; + + logger.debug({ + at: "Swap/auth", + message: "Response data", + responseJson, + }); + response.status(200).json(responseJson); + } catch (error: unknown) { + return handleErrorCondition("swap/auth", response, logger, error); + } +}; + +export default handler; diff --git a/scripts/tests/_swap-utils.ts b/scripts/tests/_swap-utils.ts index 48d0fa14f..80d2fed66 100644 --- a/scripts/tests/_swap-utils.ts +++ b/scripts/tests/_swap-utils.ts @@ -170,7 +170,7 @@ export function filterTestCases( return filteredTestCases; } -export async function swap(slug: "approval" | "permit") { +export async function swap(slug: "approval" | "permit" | "auth") { const filterString = process.argv[2]; const testCases = [...MIN_OUTPUT_CASES, ...EXACT_OUTPUT_CASES]; const filteredTestCases = filterTestCases(testCases, filterString); diff --git a/scripts/tests/swap-auth.ts b/scripts/tests/swap-auth.ts new file mode 100644 index 000000000..ae676ba54 --- /dev/null +++ b/scripts/tests/swap-auth.ts @@ -0,0 +1,15 @@ +import { swap } from "./_swap-utils"; + +async function swapWithAuthorization() { + console.log("Swapping with authorization..."); + await swap("auth"); +} + +swapWithAuthorization() + .then(() => console.log("Done")) + .catch((e) => { + console.error(e); + if (e.response?.data) { + console.log("Tx for debug sim:", e.response.data.transaction); + } + }); From 75fbc4428734727b902fc39b6590b0f32d999a03 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Mon, 30 Dec 2024 19:01:39 +0200 Subject: [PATCH 08/20] edits for relay flow --- api/_spoke-pool-periphery.ts | 46 +++++++++++++ api/relay/_types.ts | 2 +- api/relay/_utils.ts | 74 ++++++++++++++++++--- api/swap/auth/_utils.ts | 121 ++++++++++++++++++++++------------- api/swap/auth/index.ts | 12 +++- scripts/tests/_swap-utils.ts | 4 +- scripts/tests/swap-permit.ts | 2 +- 7 files changed, 199 insertions(+), 62 deletions(-) diff --git a/api/_spoke-pool-periphery.ts b/api/_spoke-pool-periphery.ts index 38c7605ba..e1d0a5065 100644 --- a/api/_spoke-pool-periphery.ts +++ b/api/_spoke-pool-periphery.ts @@ -260,3 +260,49 @@ export function encodeSwapAndBridgeWithPermitCalldata(args: { ] ); } + +export function encodeDepositWithAuthCalldata(args: { + signatureOwner: string; + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct; + validAfter: number; + validBefore: number; + nonce: string; + receiveWithAuthSignature: string; + depositDataSignature: string; +}) { + return SpokePoolV3Periphery__factory.createInterface().encodeFunctionData( + "depositWithAuthorization", + [ + args.signatureOwner, + args.depositData, + args.validAfter, + args.validBefore, + args.nonce, + args.receiveWithAuthSignature, + args.depositDataSignature, + ] + ); +} + +export function encodeSwapAndBridgeWithAuthCalldata(args: { + signatureOwner: string; + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct; + validAfter: number; + validBefore: number; + nonce: string; + receiveWithAuthSignature: string; + depositDataSignature: string; +}) { + return SpokePoolV3Periphery__factory.createInterface().encodeFunctionData( + "swapAndBridgeWithAuthorization", + [ + args.signatureOwner, + args.swapAndDepositData, + args.validAfter, + args.validBefore, + args.nonce, + args.receiveWithAuthSignature, + args.depositDataSignature, + ] + ); +} diff --git a/api/relay/_types.ts b/api/relay/_types.ts index 839934d49..95ea1076c 100644 --- a/api/relay/_types.ts +++ b/api/relay/_types.ts @@ -7,7 +7,7 @@ export type RelayRequest = { to: string; methodNameAndArgs: ReturnType; signatures: { - permit: string; + permit: string; // use this for all auth signatures deposit: string; }; }; diff --git a/api/relay/_utils.ts b/api/relay/_utils.ts index 82e41ab49..cdad65a78 100644 --- a/api/relay/_utils.ts +++ b/api/relay/_utils.ts @@ -5,7 +5,9 @@ import { hexString, positiveIntStr, validAddress } from "../_utils"; import { getPermitTypedData } from "../_permit"; import { InvalidParamError } from "../_errors"; import { + encodeDepositWithAuthCalldata, encodeDepositWithPermitCalldata, + encodeSwapAndBridgeWithAuthCalldata, encodeSwapAndBridgeWithPermitCalldata, getDepositTypedData, getSwapAndDepositTypedData, @@ -47,13 +49,15 @@ const SwapAndDepositDataSchema = type({ routerCalldata: hexString(), }); +const DepositDataSchema = type({ + submissionFees: SubmissionFeesSchema, + baseDepositData: BaseDepositDataSchema, + inputAmount: positiveIntStr(), +}); + export const DepositWithPermitArgsSchema = type({ signatureOwner: validAddress(), - depositData: type({ - submissionFees: SubmissionFeesSchema, - baseDepositData: BaseDepositDataSchema, - inputAmount: positiveIntStr(), - }), + depositData: DepositDataSchema, deadline: positiveIntStr(), }); @@ -63,12 +67,33 @@ export const SwapAndDepositWithPermitArgsSchema = type({ deadline: positiveIntStr(), }); +export const DepositWithAuthArgsSchema = type({ + signatureOwner: validAddress(), + depositData: DepositDataSchema, + validAfter: positiveIntStr(), + validBefore: positiveIntStr(), + nonce: hexString(), +}); + +export const SwapAndDepositWithAuthArgsSchema = type({ + signatureOwner: validAddress(), + swapAndDepositData: SwapAndDepositDataSchema, + validAfter: positiveIntStr(), + validBefore: positiveIntStr(), + nonce: hexString(), +}); + export const allowedMethodNames = [ "depositWithPermit", "swapAndBridgeWithPermit", -]; - -export function validateMethodArgs(methodName: string, args: any) { + "depositWithAuth", + "swapAndBridgeWithAuth", +] as const; + +export function validateMethodArgs( + methodName: (typeof allowedMethodNames)[number], + args: any +) { if (methodName === "depositWithPermit") { assert(args, DepositWithPermitArgsSchema); return { @@ -81,6 +106,18 @@ export function validateMethodArgs(methodName: string, args: any) { args: args as Infer, methodName, } as const; + } else if (methodName === "depositWithAuth") { + assert(args, DepositWithAuthArgsSchema); + return { + args: args as Infer, + methodName, + } as const; + } else if (methodName === "swapAndBridgeWithAuth") { + assert(args, SwapAndDepositWithAuthArgsSchema); + return { + args: args as Infer, + methodName, + } as const; } throw new Error(`Invalid method name: ${methodName}`); } @@ -190,12 +227,29 @@ export function encodeCalldataForRelayRequest(request: RelayRequest) { swapAndDepositDataSignature: request.signatures.deposit, permitSignature: request.signatures.permit, }); + } else if (request.methodNameAndArgs.methodName === "depositWithAuth") { + encodedCalldata = encodeDepositWithAuthCalldata({ + ...request.methodNameAndArgs.args, + validAfter: Number(request.methodNameAndArgs.args.validAfter), + validBefore: Number(request.methodNameAndArgs.args.validAfter), + nonce: request.methodNameAndArgs.args.nonce, + receiveWithAuthSignature: request.signatures.deposit, + depositDataSignature: request.signatures.permit, + }); + } else if (request.methodNameAndArgs.methodName === "swapAndBridgeWithAuth") { + encodedCalldata = encodeSwapAndBridgeWithAuthCalldata({ + ...request.methodNameAndArgs.args, + validAfter: Number(request.methodNameAndArgs.args.validAfter), + validBefore: Number(request.methodNameAndArgs.args.validAfter), + nonce: request.methodNameAndArgs.args.nonce, + receiveWithAuthSignature: request.signatures.deposit, + depositDataSignature: request.signatures.permit, + }); } - // TODO: Add cases for `withAuth` and `withPermit2` + // TODO: Add cases for `withPermit2` else { throw new Error(`Can not encode calldata for relay request`); } - return encodedCalldata; } diff --git a/api/swap/auth/_utils.ts b/api/swap/auth/_utils.ts index bffaca856..410a7db35 100644 --- a/api/swap/auth/_utils.ts +++ b/api/swap/auth/_utils.ts @@ -7,30 +7,63 @@ import { getTransferWithAuthTypedData } from "../../_transfer-with-auth"; import { getDepositTypedData, getSwapAndDepositTypedData, - TransferType, } from "../../_spoke-pool-periphery"; -import { extractDepositDataStruct } from "../../_dexes/utils"; -import { BigNumber, utils } from "ethers"; +import { + extractDepositDataStruct, + extractSwapAndDepositDataStruct, +} from "../../_dexes/utils"; +import { BigNumberish, BytesLike, utils } from "ethers"; +import { SpokePoolV3PeripheryInterface } from "../../_typechain/SpokePoolV3Periphery"; -export async function buildAuthTxPayload( - crossSwapQuotes: CrossSwapQuotes, - authDeadline: number, // maybe milliseconds - authStart = 0 // maybe milliseconds -) { +export async function buildAuthTxPayload({ + crossSwapQuotes, + authDeadline, + authStart = 0, + submissionFees, +}: { + crossSwapQuotes: CrossSwapQuotes; + authDeadline: number; // maybe milliseconds + authStart?: number; // maybe milliseconds + submissionFees?: { + amount: BigNumberish; + recipient: string; + }; +}) { const { originSwapQuote, bridgeQuote, crossSwap, contracts } = crossSwapQuotes; const originChainId = crossSwap.inputToken.chainId; const { originSwapEntryPoint, depositEntryPoint, originRouter } = contracts; - const baseDepositData = await extractDepositDataStruct(crossSwapQuotes); - let entryPointContract: | DepositEntryPointContract | OriginSwapEntryPointContract; let getDepositTypedDataPromise: | ReturnType | ReturnType; - let methodName: string; + let methodNameAndArgsWithoutSignatures: + | { + methodName: "depositWithAuthorization"; + argsWithoutSignatures: { + signatureOwner: string; + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct; + validAfter: BigNumberish; + validBefore: BigNumberish; + nonce: BytesLike; + }; + } + | { + methodName: "swapAndBridgeWithAuthorization"; + argsWithoutSignatures: { + signatureOwner: string; + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct; + validAfter: BigNumberish; + validBefore: BigNumberish; + nonce: BytesLike; + }; + }; + + // random non-sequesntial nonce + const nonce = utils.hexlify(utils.randomBytes(32)); if (originSwapQuote) { if (!originSwapEntryPoint) { @@ -50,29 +83,24 @@ export async function buildAuthTxPayload( `'originRouter' needs to be defined for origin swap quotes` ); } - + const swapAndDepositData = + await extractSwapAndDepositDataStruct(crossSwapQuotes); entryPointContract = originSwapEntryPoint; + getDepositTypedDataPromise = getSwapAndDepositTypedData({ - swapAndDepositData: { - // TODO: Make this dynamic - submissionFees: { - amount: BigNumber.from(0), - recipient: crossSwapQuotes.crossSwap.depositor, - }, - depositData: baseDepositData, - swapToken: originSwapQuote.tokenIn.address, - swapTokenAmount: originSwapQuote.maximumAmountIn, - minExpectedInputTokenAmount: originSwapQuote.minAmountOut, - routerCalldata: originSwapQuote.swapTx.data, - exchange: originRouter.address, - transferType: - originRouter.name === "UniswapV3UniversalRouter" - ? TransferType.Transfer - : TransferType.Approval, - }, + swapAndDepositData: swapAndDepositData, chainId: originChainId, }); - methodName = "swapAndBridgeWithAuthorization"; + methodNameAndArgsWithoutSignatures = { + methodName: "swapAndBridgeWithAuthorization", + argsWithoutSignatures: { + signatureOwner: crossSwap.depositor, + swapAndDepositData, + validAfter: authStart, + validBefore: authDeadline, + nonce, + }, + }; } else { if (!depositEntryPoint) { throw new Error( @@ -85,26 +113,27 @@ export async function buildAuthTxPayload( `auth is not supported for deposit entry point contract '${depositEntryPoint.name}'` ); } - + const depositDataStruct = await extractDepositDataStruct( + crossSwapQuotes, + submissionFees + ); entryPointContract = depositEntryPoint; getDepositTypedDataPromise = getDepositTypedData({ - depositData: { - // TODO: Make this dynamic - submissionFees: { - amount: BigNumber.from(0), - recipient: crossSwap.depositor, - }, - baseDepositData, - inputAmount: BigNumber.from(bridgeQuote.inputAmount), - }, + depositData: depositDataStruct, chainId: originChainId, }); - methodName = "depositWithAuthorization"; + methodNameAndArgsWithoutSignatures = { + methodName: "depositWithAuthorization", + argsWithoutSignatures: { + signatureOwner: crossSwap.depositor, + depositData: depositDataStruct, + validAfter: authStart, + validBefore: authDeadline, + nonce, + }, + }; } - // random non-sequesntial nonce - const nonce = utils.hexlify(utils.randomBytes(32)); - const [authTypedData, depositTypedData] = await Promise.all([ getTransferWithAuthTypedData({ tokenAddress: @@ -119,6 +148,7 @@ export async function buildAuthTxPayload( }), getDepositTypedDataPromise, ]); + return { eip712: { transferWithAuthorization: authTypedData.eip712, @@ -127,7 +157,8 @@ export async function buildAuthTxPayload( swapTx: { chainId: originChainId, to: entryPointContract.address, - methodName, + argsWithoutSignatures: + methodNameAndArgsWithoutSignatures.argsWithoutSignatures, }, }; } diff --git a/api/swap/auth/index.ts b/api/swap/auth/index.ts index 6cc971746..e74fa7b96 100644 --- a/api/swap/auth/index.ts +++ b/api/swap/auth/index.ts @@ -9,6 +9,7 @@ import { getSwapRouter02Strategy } from "../../_dexes/uniswap/swap-router-02"; import { InvalidParamError } from "../../_errors"; import { QuoteFetchStrategies } from "../../_dexes/utils"; import { buildAuthTxPayload } from "./_utils"; +import { GAS_SPONSOR_ADDRESS } from "../../relay/_utils"; export const authSwapQueryParamsSchema = type({ authDeadline: optional(positiveIntStr()), @@ -90,11 +91,16 @@ const handler = async ( quoteFetchStrategies ); // Build tx for auth - const crossSwapTxForAuth = await buildAuthTxPayload( + const crossSwapTxForAuth = await buildAuthTxPayload({ crossSwapQuotes, authDeadline, - authStart - ); + authStart, + // FIXME: Calculate proper fees + submissionFees: { + amount: "0", + recipient: GAS_SPONSOR_ADDRESS, + }, + }); const responseJson = crossSwapTxForAuth; diff --git a/scripts/tests/_swap-utils.ts b/scripts/tests/_swap-utils.ts index 05e24b508..d1faf7d7a 100644 --- a/scripts/tests/_swap-utils.ts +++ b/scripts/tests/_swap-utils.ts @@ -169,7 +169,7 @@ export function filterTestCases( return filteredTestCases; } -export async function swap(slug: "approval" | "permit" | "auth") { +export async function fetchSwapQuote(slug: "approval" | "permit" | "auth") { const filterString = process.argv[2]; const testCases = [...MIN_OUTPUT_CASES, ...EXACT_OUTPUT_CASES]; const filteredTestCases = filterTestCases(testCases, filterString); @@ -180,6 +180,6 @@ export async function swap(slug: "approval" | "permit" | "auth") { params: testCase.params, }); console.log(response.data); - return response.data; + return response.data as T; } } diff --git a/scripts/tests/swap-permit.ts b/scripts/tests/swap-permit.ts index ab052e48e..c9c7ab86b 100644 --- a/scripts/tests/swap-permit.ts +++ b/scripts/tests/swap-permit.ts @@ -6,7 +6,7 @@ import axios from "axios"; async function swapWithPermit() { console.log("Swapping with permit..."); - const swapQuote = await fetchSwapQuote("permit"); + const swapQuote = await fetchSwapQuote("permit"); if (process.env.DEV_WALLET_PK) { const wallet = new Wallet(process.env.DEV_WALLET_PK!).connect( From 01422866d1395dc78c5efbe8ca67c09411420ef6 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Mon, 30 Dec 2024 19:12:49 +0200 Subject: [PATCH 09/20] add test script --- scripts/tests/swap-auth.ts | 64 +++++++++++++++++++++++++++++++++--- scripts/tests/swap-permit.ts | 10 +++++- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/scripts/tests/swap-auth.ts b/scripts/tests/swap-auth.ts index ae676ba54..a772aad6e 100644 --- a/scripts/tests/swap-auth.ts +++ b/scripts/tests/swap-auth.ts @@ -1,11 +1,65 @@ -import { swap } from "./_swap-utils"; +import { Wallet } from "ethers"; -async function swapWithAuthorization() { - console.log("Swapping with authorization..."); - await swap("auth"); +import { getProvider } from "../../api/_utils"; +import { fetchSwapQuote, SWAP_API_BASE_URL } from "./_swap-utils"; +import { buildAuthTxPayload } from "../../api/swap/auth/_utils"; +import axios from "axios"; + +type AuthPayload = Awaited>; + +async function swapWithPermit() { + console.log("Swapping with auth..."); + const swapQuote = await fetchSwapQuote("auth"); + + if (!swapQuote) { + console.log("No Quote"); + return; + } + + if (process.env.DEV_WALLET_PK) { + const wallet = new Wallet(process.env.DEV_WALLET_PK!).connect( + getProvider(swapQuote.swapTx.chainId) + ); + + // sign permit + deposit + const permitSig = await wallet._signTypedData( + swapQuote.eip712.transferWithAuthorization.domain, + swapQuote.eip712.transferWithAuthorization.types, + swapQuote.eip712.transferWithAuthorization.message + ); + console.log("Signed permit:", permitSig); + + const depositSig = await wallet._signTypedData( + swapQuote.eip712.deposit.domain, + swapQuote.eip712.deposit.types, + swapQuote.eip712.deposit.message + ); + console.log("Signed deposit:", depositSig); + + // relay + const relayResponse = await axios.post(`${SWAP_API_BASE_URL}/api/relay`, { + ...swapQuote.swapTx, + signatures: { permit: permitSig, deposit: depositSig }, + }); + console.log("Relay response:", relayResponse.data); + + // track relay + while (true) { + const relayStatusResponse = await axios.get( + `${SWAP_API_BASE_URL}/api/relay/status?requestHash=${relayResponse.data.requestHash}` + ); + console.log("Relay status response:", relayStatusResponse.data); + + if (relayStatusResponse.data.status === "success") { + break; + } + + await new Promise((resolve) => setTimeout(resolve, 1_000)); + } + } } -swapWithAuthorization() +swapWithPermit() .then(() => console.log("Done")) .catch((e) => { console.error(e); diff --git a/scripts/tests/swap-permit.ts b/scripts/tests/swap-permit.ts index c9c7ab86b..ce3c4e409 100644 --- a/scripts/tests/swap-permit.ts +++ b/scripts/tests/swap-permit.ts @@ -2,11 +2,19 @@ import { Wallet } from "ethers"; import { getProvider } from "../../api/_utils"; import { fetchSwapQuote, SWAP_API_BASE_URL } from "./_swap-utils"; +import { buildPermitTxPayload } from "../../api/swap/permit/_utils"; import axios from "axios"; +type PermitPayload = Awaited>; + async function swapWithPermit() { console.log("Swapping with permit..."); - const swapQuote = await fetchSwapQuote("permit"); + const swapQuote = await fetchSwapQuote("permit"); + + if (!swapQuote) { + console.log("No Quote"); + return; + } if (process.env.DEV_WALLET_PK) { const wallet = new Wallet(process.env.DEV_WALLET_PK!).connect( From 36fd4e81af74455553ac13605efba7ba5fd86da1 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Thu, 2 Jan 2025 12:09:10 +0200 Subject: [PATCH 10/20] address comments --- api/_transfer-with-auth.ts | 6 ------ api/_utils.ts | 5 +++++ api/relay/_utils.ts | 6 +++--- scripts/tests/swap-auth.ts | 4 ++-- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/api/_transfer-with-auth.ts b/api/_transfer-with-auth.ts index 27c39d972..eb95d1bfb 100644 --- a/api/_transfer-with-auth.ts +++ b/api/_transfer-with-auth.ts @@ -95,12 +95,6 @@ export async function getTransferWithAuthTypedData(params: { domainSeparator, eip712: { types: { - EIP712Domain: [ - { name: "name", type: "string" }, - { name: "version", type: "string" }, - { name: "chainId", type: "uint256" }, - { name: "verifyingContract", type: "address" }, - ], TransferWithAuthorization: [ { name: "from", type: "address" }, { name: "to", type: "address" }, diff --git a/api/_utils.ts b/api/_utils.ts index 238c8687f..ac025eb3d 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -34,6 +34,7 @@ import { Infer, integer, min, + size, string, Struct, } from "superstruct"; @@ -1509,6 +1510,10 @@ export function hexString() { }); } +export function bytes32() { + return size(hexString(), 66); // inclusive of "0x" +} + /** * Returns the cushion for a given token symbol and route. If no route is specified, the cushion for the token symbol * @param symbol The token symbol diff --git a/api/relay/_utils.ts b/api/relay/_utils.ts index cdad65a78..4af4f26a7 100644 --- a/api/relay/_utils.ts +++ b/api/relay/_utils.ts @@ -1,7 +1,7 @@ import { assert, Infer, type } from "superstruct"; import { utils } from "ethers"; -import { hexString, positiveIntStr, validAddress } from "../_utils"; +import { bytes32, hexString, positiveIntStr, validAddress } from "../_utils"; import { getPermitTypedData } from "../_permit"; import { InvalidParamError } from "../_errors"; import { @@ -72,7 +72,7 @@ export const DepositWithAuthArgsSchema = type({ depositData: DepositDataSchema, validAfter: positiveIntStr(), validBefore: positiveIntStr(), - nonce: hexString(), + nonce: bytes32(), }); export const SwapAndDepositWithAuthArgsSchema = type({ @@ -80,7 +80,7 @@ export const SwapAndDepositWithAuthArgsSchema = type({ swapAndDepositData: SwapAndDepositDataSchema, validAfter: positiveIntStr(), validBefore: positiveIntStr(), - nonce: hexString(), + nonce: bytes32(), }); export const allowedMethodNames = [ diff --git a/scripts/tests/swap-auth.ts b/scripts/tests/swap-auth.ts index a772aad6e..2e1bcb256 100644 --- a/scripts/tests/swap-auth.ts +++ b/scripts/tests/swap-auth.ts @@ -7,7 +7,7 @@ import axios from "axios"; type AuthPayload = Awaited>; -async function swapWithPermit() { +async function swapWithAuth() { console.log("Swapping with auth..."); const swapQuote = await fetchSwapQuote("auth"); @@ -59,7 +59,7 @@ async function swapWithPermit() { } } -swapWithPermit() +swapWithAuth() .then(() => console.log("Done")) .catch((e) => { console.error(e); From 5a6c1eb469b4ece9d8240eff8dad0a9fd74f3aee Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Thu, 2 Jan 2025 13:04:20 +0200 Subject: [PATCH 11/20] stringify bignums --- api/swap/auth/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/api/swap/auth/index.ts b/api/swap/auth/index.ts index e74fa7b96..40b6aefc0 100644 --- a/api/swap/auth/index.ts +++ b/api/swap/auth/index.ts @@ -4,7 +4,11 @@ import { assert, Infer, optional, type } from "superstruct"; import { TypedVercelRequest } from "../../_types"; import { getLogger, handleErrorCondition, positiveIntStr } from "../../_utils"; import { getCrossSwapQuotes } from "../../_dexes/cross-swap-service"; -import { handleBaseSwapQueryParams, BaseSwapQueryParams } from "../_utils"; +import { + handleBaseSwapQueryParams, + BaseSwapQueryParams, + stringifyBigNumProps, +} from "../_utils"; import { getSwapRouter02Strategy } from "../../_dexes/uniswap/swap-router-02"; import { InvalidParamError } from "../../_errors"; import { QuoteFetchStrategies } from "../../_dexes/utils"; @@ -102,7 +106,7 @@ const handler = async ( }, }); - const responseJson = crossSwapTxForAuth; + const responseJson = stringifyBigNumProps(crossSwapTxForAuth); logger.debug({ at: "Swap/auth", From 3701103a5a5f89c2939b9d7037afea1a8cf3a4db Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Thu, 2 Jan 2025 13:57:15 +0200 Subject: [PATCH 12/20] fix serializer function to properly handle arrays --- api/swap/_utils.ts | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/api/swap/_utils.ts b/api/swap/_utils.ts index bc5eda0ba..3b42f41f0 100644 --- a/api/swap/_utils.ts +++ b/api/swap/_utils.ts @@ -237,16 +237,28 @@ export async function calculateCrossSwapFees( }; } -export function stringifyBigNumProps(value: object): object { +export function stringifyBigNumProps(value: T): T { + if (Array.isArray(value)) { + return value.map((element) => { + if (element instanceof BigNumber) { + return element.toString(); + } + if (typeof element === "object" && element !== null) { + return stringifyBigNumProps(element); + } + return element; + }) as T; + } + return Object.fromEntries( - Object.entries(value).map(([key, value]) => { - if (value instanceof BigNumber) { - return [key, value.toString()]; + Object.entries(value).map(([key, val]) => { + if (val instanceof BigNumber) { + return [key, val.toString()]; } - if (typeof value === "object" && value !== null) { - return [key, stringifyBigNumProps(value)]; + if (typeof val === "object" && val !== null) { + return [key, stringifyBigNumProps(val)]; } - return [key, value]; + return [key, val]; }) - ); + ) as T; } From 9ba6cac287c80e9541de1235279d3b0e1e868f90 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Thu, 2 Jan 2025 14:39:24 +0200 Subject: [PATCH 13/20] add methodName --- api/swap/auth/_utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/swap/auth/_utils.ts b/api/swap/auth/_utils.ts index 410a7db35..d1473ef4a 100644 --- a/api/swap/auth/_utils.ts +++ b/api/swap/auth/_utils.ts @@ -154,11 +154,11 @@ export async function buildAuthTxPayload({ transferWithAuthorization: authTypedData.eip712, deposit: depositTypedData.eip712, }, + swapTx: { chainId: originChainId, to: entryPointContract.address, - argsWithoutSignatures: - methodNameAndArgsWithoutSignatures.argsWithoutSignatures, + ...methodNameAndArgsWithoutSignatures, }, }; } From 1cbee1ff216d73aab18c6406dde54d514cc9d2f4 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Fri, 3 Jan 2025 17:12:39 +0200 Subject: [PATCH 14/20] fix method names --- api/_utils.ts | 8 +++++--- api/relay/_utils.ts | 16 ++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/api/_utils.ts b/api/_utils.ts index ac025eb3d..48992fb4f 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -2255,14 +2255,16 @@ export async function getMaxFeePerGas(chainId: number): Promise { * const res = await axios.get(`${base_url}?${queryString}`) * ``` */ - export function buildSearchParams( - params: Record> + params: Record< + string, + number | string | boolean | Array + > ): string { const searchParams = new URLSearchParams(); for (const key in params) { const value = params[key]; - if (!value) continue; + if (value === undefined || value === null) continue; if (Array.isArray(value)) { value.forEach((val) => searchParams.append(key, String(val))); } else { diff --git a/api/relay/_utils.ts b/api/relay/_utils.ts index 4af4f26a7..230005279 100644 --- a/api/relay/_utils.ts +++ b/api/relay/_utils.ts @@ -86,8 +86,8 @@ export const SwapAndDepositWithAuthArgsSchema = type({ export const allowedMethodNames = [ "depositWithPermit", "swapAndBridgeWithPermit", - "depositWithAuth", - "swapAndBridgeWithAuth", + "depositWithAuthorization", + "swapAndBridgeWithAuthorization", ] as const; export function validateMethodArgs( @@ -106,13 +106,13 @@ export function validateMethodArgs( args: args as Infer, methodName, } as const; - } else if (methodName === "depositWithAuth") { + } else if (methodName === "depositWithAuthorization") { assert(args, DepositWithAuthArgsSchema); return { args: args as Infer, methodName, } as const; - } else if (methodName === "swapAndBridgeWithAuth") { + } else if (methodName === "swapAndBridgeWithAuthorization") { assert(args, SwapAndDepositWithAuthArgsSchema); return { args: args as Infer, @@ -227,7 +227,9 @@ export function encodeCalldataForRelayRequest(request: RelayRequest) { swapAndDepositDataSignature: request.signatures.deposit, permitSignature: request.signatures.permit, }); - } else if (request.methodNameAndArgs.methodName === "depositWithAuth") { + } else if ( + request.methodNameAndArgs.methodName === "depositWithAuthorization" + ) { encodedCalldata = encodeDepositWithAuthCalldata({ ...request.methodNameAndArgs.args, validAfter: Number(request.methodNameAndArgs.args.validAfter), @@ -236,7 +238,9 @@ export function encodeCalldataForRelayRequest(request: RelayRequest) { receiveWithAuthSignature: request.signatures.deposit, depositDataSignature: request.signatures.permit, }); - } else if (request.methodNameAndArgs.methodName === "swapAndBridgeWithAuth") { + } else if ( + request.methodNameAndArgs.methodName === "swapAndBridgeWithAuthorization" + ) { encodedCalldata = encodeSwapAndBridgeWithAuthCalldata({ ...request.methodNameAndArgs.args, validAfter: Number(request.methodNameAndArgs.args.validAfter), From c65212bbe63d27c5a3436ac6c73bf6d397fffb35 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Fri, 3 Jan 2025 18:03:42 +0200 Subject: [PATCH 15/20] verify signatures for transfer with auth --- api/relay/_utils.ts | 144 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 116 insertions(+), 28 deletions(-) diff --git a/api/relay/_utils.ts b/api/relay/_utils.ts index 230005279..f748931e1 100644 --- a/api/relay/_utils.ts +++ b/api/relay/_utils.ts @@ -14,6 +14,7 @@ import { } from "../_spoke-pool-periphery"; import { RelayRequest } from "./_types"; import { redisCache } from "../_cache"; +import { getTransferWithAuthTypedData } from "../_transfer-with-auth"; export const GAS_SPONSOR_ADDRESS = process.env.GAS_SPONSOR_ADDRESS ?? @@ -131,10 +132,15 @@ export async function verifySignatures({ const { methodName, args } = methodNameAndArgs; let signatureOwner: string; - let getPermitTypedDataPromise: ReturnType; + let getPermitTypedDataPromise: + | ReturnType + | undefined; let getDepositTypedDataPromise: ReturnType< typeof getDepositTypedData | typeof getSwapAndDepositTypedData >; + let getTransferWithAuthTypedDataPromise: + | ReturnType + | undefined; if (methodName === "depositWithPermit") { const { signatureOwner: _signatureOwner, deadline, depositData } = args; @@ -170,41 +176,123 @@ export async function verifySignatures({ chainId, swapAndDepositData, }); + } else if (methodName === "depositWithAuthorization") { + const { + signatureOwner: _signatureOwner, + validAfter, + validBefore, + nonce, + depositData, + } = args; + signatureOwner = _signatureOwner; + getTransferWithAuthTypedDataPromise = getTransferWithAuthTypedData({ + tokenAddress: depositData.baseDepositData.inputToken, + chainId, + ownerAddress: signatureOwner, + spenderAddress: to, // SpokePoolV3Periphery address + value: depositData.inputAmount, + validAfter: Number(validAfter), + validBefore: Number(validBefore), + nonce, + }); + getDepositTypedDataPromise = getDepositTypedData({ + chainId, + depositData, + }); + } else if (methodName === "swapAndBridgeWithAuthorization") { + const { + signatureOwner: _signatureOwner, + validAfter, + validBefore, + nonce, + swapAndDepositData, + } = args; + signatureOwner = _signatureOwner; + getTransferWithAuthTypedDataPromise = getTransferWithAuthTypedData({ + tokenAddress: swapAndDepositData.swapToken, + chainId, + ownerAddress: signatureOwner, + spenderAddress: to, // SpokePoolV3Periphery address + value: swapAndDepositData.swapTokenAmount, + validAfter: Number(validAfter), + validBefore: Number(validBefore), + nonce, + }); + getDepositTypedDataPromise = getSwapAndDepositTypedData({ + chainId, + swapAndDepositData, + }); } else { throw new Error( `Can not verify signatures for invalid method name: ${methodName}` ); } - const [permitTypedData, depositTypedData] = await Promise.all([ - getPermitTypedDataPromise, - getDepositTypedDataPromise, - ]); + if (getPermitTypedDataPromise) { + const [permitTypedData, depositTypedData] = await Promise.all([ + getPermitTypedDataPromise, + getDepositTypedDataPromise, + ]); + + const recoveredPermitSignerAddress = utils.verifyTypedData( + permitTypedData.eip712.domain, + permitTypedData.eip712.types, + permitTypedData.eip712.message, + signatures.permit + ); - const recoveredPermitSignerAddress = utils.verifyTypedData( - permitTypedData.eip712.domain, - permitTypedData.eip712.types, - permitTypedData.eip712.message, - signatures.permit - ); - if (recoveredPermitSignerAddress !== signatureOwner) { - throw new InvalidParamError({ - message: "Invalid permit signature", - param: "signatures.permit", - }); - } + if (recoveredPermitSignerAddress !== signatureOwner) { + throw new InvalidParamError({ + message: "Invalid permit signature", + param: "signatures.permit", + }); + } - const recoveredDepositSignerAddress = utils.verifyTypedData( - depositTypedData.eip712.domain, - depositTypedData.eip712.types, - depositTypedData.eip712.message, - signatures.deposit - ); - if (recoveredDepositSignerAddress !== signatureOwner) { - throw new InvalidParamError({ - message: "Invalid deposit signature", - param: "signatures.deposit", - }); + const recoveredDepositSignerAddress = utils.verifyTypedData( + depositTypedData.eip712.domain, + depositTypedData.eip712.types, + depositTypedData.eip712.message, + signatures.deposit + ); + if (recoveredDepositSignerAddress !== signatureOwner) { + throw new InvalidParamError({ + message: "Invalid deposit signature", + param: "signatures.deposit", + }); + } + } else if (getTransferWithAuthTypedDataPromise) { + const [authTypedData, depositTypedData] = await Promise.all([ + getTransferWithAuthTypedDataPromise, + getDepositTypedDataPromise, + ]); + + const recoveredAuthSignerAddress = utils.verifyTypedData( + authTypedData.eip712.domain, + authTypedData.eip712.types, + authTypedData.eip712.message, + signatures.permit + ); + + if (recoveredAuthSignerAddress !== signatureOwner) { + throw new InvalidParamError({ + message: "Invalid Authorization signature", + param: "signatures.permit", + }); + } + + const recoveredDepositSignerAddress = utils.verifyTypedData( + depositTypedData.eip712.domain, + depositTypedData.eip712.types, + depositTypedData.eip712.message, + signatures.deposit + ); + + if (recoveredDepositSignerAddress !== signatureOwner) { + throw new InvalidParamError({ + message: "Invalid deposit signature", + param: "signatures.deposit", + }); + } } } From a8ef89146b87cc7418598e1415961353aa4d8cbe Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Fri, 3 Jan 2025 18:24:04 +0200 Subject: [PATCH 16/20] fix --- api/relay/_utils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/relay/_utils.ts b/api/relay/_utils.ts index f748931e1..cbb7890a0 100644 --- a/api/relay/_utils.ts +++ b/api/relay/_utils.ts @@ -323,8 +323,8 @@ export function encodeCalldataForRelayRequest(request: RelayRequest) { validAfter: Number(request.methodNameAndArgs.args.validAfter), validBefore: Number(request.methodNameAndArgs.args.validAfter), nonce: request.methodNameAndArgs.args.nonce, - receiveWithAuthSignature: request.signatures.deposit, - depositDataSignature: request.signatures.permit, + receiveWithAuthSignature: request.signatures.permit, + depositDataSignature: request.signatures.deposit, }); } else if ( request.methodNameAndArgs.methodName === "swapAndBridgeWithAuthorization" @@ -334,8 +334,8 @@ export function encodeCalldataForRelayRequest(request: RelayRequest) { validAfter: Number(request.methodNameAndArgs.args.validAfter), validBefore: Number(request.methodNameAndArgs.args.validAfter), nonce: request.methodNameAndArgs.args.nonce, - receiveWithAuthSignature: request.signatures.deposit, - depositDataSignature: request.signatures.permit, + receiveWithAuthSignature: request.signatures.permit, + depositDataSignature: request.signatures.deposit, }); } // TODO: Add cases for `withPermit2` From 34053f372a200576c3fd6b6a29563b2e390a34bb Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Fri, 3 Jan 2025 20:02:02 +0200 Subject: [PATCH 17/20] fix timestamps for auth period --- api/_transfer-with-auth.ts | 6 ++---- api/swap/auth/_utils.ts | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/api/_transfer-with-auth.ts b/api/_transfer-with-auth.ts index eb95d1bfb..f9ee26df6 100644 --- a/api/_transfer-with-auth.ts +++ b/api/_transfer-with-auth.ts @@ -115,10 +115,8 @@ export async function getTransferWithAuthTypedData(params: { from: params.ownerAddress, to: params.spenderAddress, value: String(params.value), - validAfter: params?.validAfter - ? convertMaybeMillisecondsToSeconds(params.validAfter) - : 0, - validBefore: convertMaybeMillisecondsToSeconds(params.validBefore), + validAfter: params.validAfter ?? 0, + validBefore: params.validBefore, nonce: params.nonce, // non-sequential nonce, random 32 byte hex string }, }, diff --git a/api/swap/auth/_utils.ts b/api/swap/auth/_utils.ts index d1473ef4a..df13c0d4e 100644 --- a/api/swap/auth/_utils.ts +++ b/api/swap/auth/_utils.ts @@ -3,7 +3,10 @@ import { DepositEntryPointContract, OriginSwapEntryPointContract, } from "../../_dexes/types"; -import { getTransferWithAuthTypedData } from "../../_transfer-with-auth"; +import { + convertMaybeMillisecondsToSeconds, + getTransferWithAuthTypedData, +} from "../../_transfer-with-auth"; import { getDepositTypedData, getSwapAndDepositTypedData, @@ -65,6 +68,9 @@ export async function buildAuthTxPayload({ // random non-sequesntial nonce const nonce = utils.hexlify(utils.randomBytes(32)); + const validAfter = convertMaybeMillisecondsToSeconds(authStart); + const validBefore = convertMaybeMillisecondsToSeconds(authDeadline); + if (originSwapQuote) { if (!originSwapEntryPoint) { throw new Error( @@ -96,8 +102,8 @@ export async function buildAuthTxPayload({ argsWithoutSignatures: { signatureOwner: crossSwap.depositor, swapAndDepositData, - validAfter: authStart, - validBefore: authDeadline, + validAfter, + validBefore, nonce, }, }; @@ -127,8 +133,8 @@ export async function buildAuthTxPayload({ argsWithoutSignatures: { signatureOwner: crossSwap.depositor, depositData: depositDataStruct, - validAfter: authStart, - validBefore: authDeadline, + validAfter, + validBefore, nonce, }, }; @@ -143,8 +149,8 @@ export async function buildAuthTxPayload({ spenderAddress: entryPointContract.address, value: originSwapQuote?.maximumAmountIn || bridgeQuote.inputAmount, nonce, - validAfter: authStart, - validBefore: authDeadline, + validAfter, + validBefore, }), getDepositTypedDataPromise, ]); From b6d63c09919be3fb57eff3eae8ef17d8dba0cdb0 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Fri, 3 Jan 2025 21:08:53 +0200 Subject: [PATCH 18/20] normalize timestamps --- api/_transfer-with-auth.ts | 5 ----- api/swap/auth/_utils.ts | 13 +++++-------- api/swap/auth/index.ts | 6 +++--- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/api/_transfer-with-auth.ts b/api/_transfer-with-auth.ts index f9ee26df6..fab13ea9e 100644 --- a/api/_transfer-with-auth.ts +++ b/api/_transfer-with-auth.ts @@ -122,8 +122,3 @@ export async function getTransferWithAuthTypedData(params: { }, }; } - -export function convertMaybeMillisecondsToSeconds(timestamp: number): number { - const isMilliseconds = timestamp > 1_000_000_000; // rough approximation - return isMilliseconds ? Math.floor(timestamp / 1000) : Math.floor(timestamp); -} diff --git a/api/swap/auth/_utils.ts b/api/swap/auth/_utils.ts index df13c0d4e..dcc459428 100644 --- a/api/swap/auth/_utils.ts +++ b/api/swap/auth/_utils.ts @@ -3,10 +3,7 @@ import { DepositEntryPointContract, OriginSwapEntryPointContract, } from "../../_dexes/types"; -import { - convertMaybeMillisecondsToSeconds, - getTransferWithAuthTypedData, -} from "../../_transfer-with-auth"; +import { getTransferWithAuthTypedData } from "../../_transfer-with-auth"; import { getDepositTypedData, getSwapAndDepositTypedData, @@ -25,8 +22,8 @@ export async function buildAuthTxPayload({ submissionFees, }: { crossSwapQuotes: CrossSwapQuotes; - authDeadline: number; // maybe milliseconds - authStart?: number; // maybe milliseconds + authDeadline: number; + authStart?: number; submissionFees?: { amount: BigNumberish; recipient: string; @@ -68,8 +65,8 @@ export async function buildAuthTxPayload({ // random non-sequesntial nonce const nonce = utils.hexlify(utils.randomBytes(32)); - const validAfter = convertMaybeMillisecondsToSeconds(authStart); - const validBefore = convertMaybeMillisecondsToSeconds(authDeadline); + const validAfter = authStart; + const validBefore = authDeadline; if (originSwapQuote) { if (!originSwapEntryPoint) { diff --git a/api/swap/auth/index.ts b/api/swap/auth/index.ts index 40b6aefc0..21b919b66 100644 --- a/api/swap/auth/index.ts +++ b/api/swap/auth/index.ts @@ -14,6 +14,7 @@ import { InvalidParamError } from "../../_errors"; import { QuoteFetchStrategies } from "../../_dexes/utils"; import { buildAuthTxPayload } from "./_utils"; import { GAS_SPONSOR_ADDRESS } from "../../relay/_utils"; +import * as sdk from "@across-protocol/sdk"; export const authSwapQueryParamsSchema = type({ authDeadline: optional(positiveIntStr()), @@ -21,8 +22,7 @@ export const authSwapQueryParamsSchema = type({ export type authSwapQueryParams = Infer; -const DEFAULT_AUTH_DEADLINE = - Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365; // 1 year +const DEFAULT_AUTH_DEADLINE = sdk.utils.getCurrentTime() + 60 * 60 * 24 * 365; // 1 year // For auth-based flows, we have to use the `SpokePoolPeriphery` as an entry point const quoteFetchStrategies: QuoteFetchStrategies = { @@ -53,7 +53,7 @@ const handler = async ( authSwapQueryParamsSchema ); const authDeadline = Number(_authDeadline ?? DEFAULT_AUTH_DEADLINE); - const authStart = Number(_authStart ?? Date.now()); + const authStart = Number(_authStart ?? sdk.utils.getCurrentTime()); if (authDeadline < Math.floor(Date.now() / 1000)) { throw new InvalidParamError({ From ae2361d69fc99a34eeb5195f2091e91b03ec6330 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Fri, 3 Jan 2025 21:33:19 +0200 Subject: [PATCH 19/20] fix --- api/relay/_utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/relay/_utils.ts b/api/relay/_utils.ts index cbb7890a0..f203e6495 100644 --- a/api/relay/_utils.ts +++ b/api/relay/_utils.ts @@ -321,7 +321,7 @@ export function encodeCalldataForRelayRequest(request: RelayRequest) { encodedCalldata = encodeDepositWithAuthCalldata({ ...request.methodNameAndArgs.args, validAfter: Number(request.methodNameAndArgs.args.validAfter), - validBefore: Number(request.methodNameAndArgs.args.validAfter), + validBefore: Number(request.methodNameAndArgs.args.validBefore), nonce: request.methodNameAndArgs.args.nonce, receiveWithAuthSignature: request.signatures.permit, depositDataSignature: request.signatures.deposit, @@ -332,7 +332,7 @@ export function encodeCalldataForRelayRequest(request: RelayRequest) { encodedCalldata = encodeSwapAndBridgeWithAuthCalldata({ ...request.methodNameAndArgs.args, validAfter: Number(request.methodNameAndArgs.args.validAfter), - validBefore: Number(request.methodNameAndArgs.args.validAfter), + validBefore: Number(request.methodNameAndArgs.args.validBefore), nonce: request.methodNameAndArgs.args.nonce, receiveWithAuthSignature: request.signatures.permit, depositDataSignature: request.signatures.deposit, From 6946734188ac3a7b733e99e01d23085baa88e1bf Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Sat, 4 Jan 2025 13:17:02 +0200 Subject: [PATCH 20/20] sign with receive with auth typehash --- api/_spoke-pool-periphery.ts | 4 ++-- api/_transfer-with-auth.ts | 6 +++--- api/relay/_utils.ts | 16 ++++++++-------- api/swap/auth/_utils.ts | 6 +++--- scripts/tests/swap-auth.ts | 6 +++--- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/api/_spoke-pool-periphery.ts b/api/_spoke-pool-periphery.ts index e1d0a5065..14807851f 100644 --- a/api/_spoke-pool-periphery.ts +++ b/api/_spoke-pool-periphery.ts @@ -291,7 +291,7 @@ export function encodeSwapAndBridgeWithAuthCalldata(args: { validBefore: number; nonce: string; receiveWithAuthSignature: string; - depositDataSignature: string; + swapAndDepositDataSignature: string; }) { return SpokePoolV3Periphery__factory.createInterface().encodeFunctionData( "swapAndBridgeWithAuthorization", @@ -302,7 +302,7 @@ export function encodeSwapAndBridgeWithAuthCalldata(args: { args.validBefore, args.nonce, args.receiveWithAuthSignature, - args.depositDataSignature, + args.swapAndDepositDataSignature, ] ); } diff --git a/api/_transfer-with-auth.ts b/api/_transfer-with-auth.ts index fab13ea9e..a14a3b6d2 100644 --- a/api/_transfer-with-auth.ts +++ b/api/_transfer-with-auth.ts @@ -25,7 +25,7 @@ export function hashDomainSeparator(params: { ); } -export async function getTransferWithAuthTypedData(params: { +export async function getReceiveWithAuthTypedData(params: { tokenAddress: string; chainId: number; ownerAddress: string; @@ -95,7 +95,7 @@ export async function getTransferWithAuthTypedData(params: { domainSeparator, eip712: { types: { - TransferWithAuthorization: [ + ReceiveWithAuthorization: [ { name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" }, @@ -110,7 +110,7 @@ export async function getTransferWithAuthTypedData(params: { chainId: params.chainId, verifyingContract: params.tokenAddress, }, - primaryType: "TransferWithAuthorization", + primaryType: "ReceiveWithAuthorization", message: { from: params.ownerAddress, to: params.spenderAddress, diff --git a/api/relay/_utils.ts b/api/relay/_utils.ts index f203e6495..de5465602 100644 --- a/api/relay/_utils.ts +++ b/api/relay/_utils.ts @@ -14,7 +14,7 @@ import { } from "../_spoke-pool-periphery"; import { RelayRequest } from "./_types"; import { redisCache } from "../_cache"; -import { getTransferWithAuthTypedData } from "../_transfer-with-auth"; +import { getReceiveWithAuthTypedData } from "../_transfer-with-auth"; export const GAS_SPONSOR_ADDRESS = process.env.GAS_SPONSOR_ADDRESS ?? @@ -138,8 +138,8 @@ export async function verifySignatures({ let getDepositTypedDataPromise: ReturnType< typeof getDepositTypedData | typeof getSwapAndDepositTypedData >; - let getTransferWithAuthTypedDataPromise: - | ReturnType + let getReceiveWithAuthTypedDataPromise: + | ReturnType | undefined; if (methodName === "depositWithPermit") { @@ -185,7 +185,7 @@ export async function verifySignatures({ depositData, } = args; signatureOwner = _signatureOwner; - getTransferWithAuthTypedDataPromise = getTransferWithAuthTypedData({ + getReceiveWithAuthTypedDataPromise = getReceiveWithAuthTypedData({ tokenAddress: depositData.baseDepositData.inputToken, chainId, ownerAddress: signatureOwner, @@ -208,7 +208,7 @@ export async function verifySignatures({ swapAndDepositData, } = args; signatureOwner = _signatureOwner; - getTransferWithAuthTypedDataPromise = getTransferWithAuthTypedData({ + getReceiveWithAuthTypedDataPromise = getReceiveWithAuthTypedData({ tokenAddress: swapAndDepositData.swapToken, chainId, ownerAddress: signatureOwner, @@ -260,9 +260,9 @@ export async function verifySignatures({ param: "signatures.deposit", }); } - } else if (getTransferWithAuthTypedDataPromise) { + } else if (getReceiveWithAuthTypedDataPromise) { const [authTypedData, depositTypedData] = await Promise.all([ - getTransferWithAuthTypedDataPromise, + getReceiveWithAuthTypedDataPromise, getDepositTypedDataPromise, ]); @@ -335,7 +335,7 @@ export function encodeCalldataForRelayRequest(request: RelayRequest) { validBefore: Number(request.methodNameAndArgs.args.validBefore), nonce: request.methodNameAndArgs.args.nonce, receiveWithAuthSignature: request.signatures.permit, - depositDataSignature: request.signatures.deposit, + swapAndDepositDataSignature: request.signatures.deposit, }); } // TODO: Add cases for `withPermit2` diff --git a/api/swap/auth/_utils.ts b/api/swap/auth/_utils.ts index dcc459428..323e7a43a 100644 --- a/api/swap/auth/_utils.ts +++ b/api/swap/auth/_utils.ts @@ -3,7 +3,7 @@ import { DepositEntryPointContract, OriginSwapEntryPointContract, } from "../../_dexes/types"; -import { getTransferWithAuthTypedData } from "../../_transfer-with-auth"; +import { getReceiveWithAuthTypedData } from "../../_transfer-with-auth"; import { getDepositTypedData, getSwapAndDepositTypedData, @@ -138,7 +138,7 @@ export async function buildAuthTxPayload({ } const [authTypedData, depositTypedData] = await Promise.all([ - getTransferWithAuthTypedData({ + getReceiveWithAuthTypedData({ tokenAddress: originSwapQuote?.tokenIn.address || bridgeQuote.inputToken.address, chainId: originChainId, @@ -154,7 +154,7 @@ export async function buildAuthTxPayload({ return { eip712: { - transferWithAuthorization: authTypedData.eip712, + receiveWithAuthorization: authTypedData.eip712, deposit: depositTypedData.eip712, }, diff --git a/scripts/tests/swap-auth.ts b/scripts/tests/swap-auth.ts index 2e1bcb256..31a6e2467 100644 --- a/scripts/tests/swap-auth.ts +++ b/scripts/tests/swap-auth.ts @@ -23,9 +23,9 @@ async function swapWithAuth() { // sign permit + deposit const permitSig = await wallet._signTypedData( - swapQuote.eip712.transferWithAuthorization.domain, - swapQuote.eip712.transferWithAuthorization.types, - swapQuote.eip712.transferWithAuthorization.message + swapQuote.eip712.receiveWithAuthorization.domain, + swapQuote.eip712.receiveWithAuthorization.types, + swapQuote.eip712.receiveWithAuthorization.message ); console.log("Signed permit:", permitSig);