diff --git a/.github/workflows/dependency-review.yaml b/.github/workflows/dependency-review.yaml index f4f824e7c..ca75093f2 100644 --- a/.github/workflows/dependency-review.yaml +++ b/.github/workflows/dependency-review.yaml @@ -16,6 +16,8 @@ jobs: with: fail-on-severity: high # Comma-separated list of GHSA IDs to allow. - # We allow @openzeppelin/contracts@3.4.1-solc-0.7-2 critical vulnerabilities + # We allow the following critical vulnerabilities # because they are not exploitable in our usage of the package. - allow-ghsas: GHSA-fg47-3c2x-m2wr, GHSA-88g8-f5mf-f5rj, GHSA-3h5v-q93c-6h6q + # - @openzeppelin/contracts@3.4.1-solc-0.7-2 + # - @openzeppelin/contracts@4.7.0 + allow-ghsas: GHSA-fg47-3c2x-m2wr, GHSA-88g8-f5mf-f5rj, GHSA-3h5v-q93c-6h6q, GHSA-qh9x-gcfh-pcrw, GHSA-4g63-c64m-25w9, GHSA-xrc4-737v-9q75, GHSA-4h98-2769-gh6h, GHSA-4h98-2769-gh6h, GHSA-93hq-5wgc-jc82 diff --git a/api/_abis.ts b/api/_abis.ts index da0bf6884..8fe7a37ff 100644 --- a/api/_abis.ts +++ b/api/_abis.ts @@ -77,3 +77,82 @@ 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", + }, +]; + +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/_cache.ts b/api/_cache.ts index 2380cbc62..d637dc667 100644 --- a/api/_cache.ts +++ b/api/_cache.ts @@ -61,6 +61,13 @@ export class RedisCache implements interfaces.CachingMechanismInterface { throw error; } } + + async del(key: string) { + if (!this.client) { + return; + } + await this.client.del(key); + } } export const redisCache = new RedisCache(); diff --git a/api/_dexes/1inch.ts b/api/_dexes/1inch.ts index 98b47026e..f87c2e79c 100644 --- a/api/_dexes/1inch.ts +++ b/api/_dexes/1inch.ts @@ -1,27 +1,24 @@ import axios from "axios"; -import { AcrossSwap, SwapQuoteAndCalldata } from "./types"; -import { getSwapAndBridgeAddress } from "./utils"; +import { Swap, OriginSwapQuoteAndCalldata } from "./types"; +import { getSwapAndBridgeAddress } from "../_swap-and-bridge"; -export async function get1inchQuoteAndCalldata( - swap: AcrossSwap -): Promise { - const swapAndBridgeAddress = getSwapAndBridgeAddress( - "1inch", - swap.swapToken.chainId - ); - const apiBaseUrl = `https://api.1inch.dev/swap/v6.0/${swap.swapToken.chainId}`; +export async function get1inchQuoteForOriginSwapExactInput( + swap: Omit +): Promise { + const swapAndBridgeAddress = getSwapAndBridgeAddress("1inch", swap.chainId); + const apiBaseUrl = `https://api.1inch.dev/swap/v6.0/${swap.chainId}`; const apiHeaders = { Authorization: `Bearer ${process.env.ONEINCH_API_KEY}`, accept: "application/json", }; const swapParams = { - src: swap.swapToken.address, - dst: swap.acrossInputToken.address, - amount: swap.swapTokenAmount, + src: swap.tokenIn.address, + dst: swap.tokenOut.address, + amount: swap.amount, from: swapAndBridgeAddress, - slippage: swap.slippage, + slippage: swap.slippageTolerance, disableEstimate: true, allowPartialFill: false, receiver: swapAndBridgeAddress, @@ -47,6 +44,6 @@ export async function get1inchQuoteAndCalldata( value: response.data.tx.value, swapAndBridgeAddress, dex: "1inch", - slippage: swap.slippage, + slippage: swap.slippageTolerance, }; } diff --git a/api/_dexes/cross-swap-service.ts b/api/_dexes/cross-swap-service.ts new file mode 100644 index 000000000..d1f3dbfd9 --- /dev/null +++ b/api/_dexes/cross-swap-service.ts @@ -0,0 +1,631 @@ +import { TradeType } from "@uniswap/sdk-core"; + +import { + getBridgeQuoteForMinOutput, + getRouteByInputTokenAndDestinationChain, + getRouteByOutputTokenAndOriginChain, + getRoutesByChainIds, + getTokenByAddress, + Profiler, + addMarkupToAmount, +} from "../_utils"; +import { CrossSwap, CrossSwapQuotes, QuoteFetchStrategy } from "./types"; +import { + buildExactOutputBridgeTokenMessage, + buildMinOutputBridgeTokenMessage, + 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 + +export async function getCrossSwapQuotes( + crossSwap: CrossSwap, + 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/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, + destinationSwapChainId + ); + + if (!bridgeRoute) { + throw new Error( + `No bridge route found for input token ${crossSwap.inputToken.symbol} ` + + `${crossSwap.inputToken.chainId} -> ${destinationSwapChainId}` + ); + } + + const _bridgeableOutputToken = getTokenByAddress( + bridgeRoute.toTokenAddress, + bridgeRoute.toChain + ); + + if (!_bridgeableOutputToken) { + throw new Error( + `No bridgeable output token found for ${bridgeRoute.toTokenAddress} on chain ${bridgeRoute.toChain}` + ); + } + + const bridgeableOutputToken = { + address: bridgeRoute.toTokenAddress, + decimals: _bridgeableOutputToken.decimals, + symbol: _bridgeableOutputToken.symbol, + chainId: destinationSwapChainId, + }; + + const destinationSwap = { + chainId: destinationSwapChainId, + tokenIn: bridgeableOutputToken, + tokenOut: crossSwap.outputToken, + recipient: getMultiCallHandlerAddress(destinationSwapChainId), + 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( + destinationStrategy.fetchFn( + { + ...destinationSwap, + amount: crossSwap.amount.toString(), + }, + TradeType.EXACT_OUTPUT, + { + useIndicativeQuote: true, + } + ), + "INDICATIVE_getDestinationSwapQuote" + ); + + // 2. Fetch REAL destination swap quote and bridge quote in parallel to improve performance. + const [destinationSwapQuote, bridgeQuote] = await profiler.measureAsync( + Promise.all([ + // 2.1. REAL destination swap quote for bridgeable output token -> any token. + // Quote contains calldata. + destinationStrategy.fetchFn( + { + ...destinationSwap, + amount: crossSwap.amount.toString(), + }, + TradeType.EXACT_OUTPUT + ), + // 2.2. Bridge quote for bridgeable input token -> bridgeable output token based on + // indicative destination swap quote. + getBridgeQuoteForMinOutput({ + inputToken: crossSwap.inputToken, + outputToken: bridgeableOutputToken, + minOutputAmount: indicativeDestinationSwapQuote.maximumAmountIn, + recipient: getMultiCallHandlerAddress(destinationSwapChainId), + message: buildDestinationSwapCrossChainMessage({ + crossSwap, + destinationSwapQuote: indicativeDestinationSwapQuote, + bridgeableOutputToken, + routerAddress: destinationRouter.address, + }), + }), + ]), + "getAllQuotes" + ); + assertMinOutputAmount(destinationSwapQuote.minAmountOut, crossSwap.amount); + assertMinOutputAmount( + bridgeQuote.outputAmount, + destinationSwapQuote.maximumAmountIn + ); + + bridgeQuote.message = buildDestinationSwapCrossChainMessage({ + crossSwap, + destinationSwapQuote, + bridgeableOutputToken, + routerAddress: destinationRouter.address, + }); + + return { + crossSwap, + bridgeQuote, + destinationSwapQuote, + originSwapQuote: undefined, + contracts: { + originRouter, + destinationRouter, + depositEntryPoint, + }, + }; +} + +export async function getCrossSwapQuotesForOutputA2B( + crossSwap: CrossSwap, + strategies: QuoteFetchStrategies +) { + const profiler = new Profiler({ + 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, + originSwapChainId + ); + + if (!bridgeRoute) { + throw new Error( + `No bridge route found for output token ${crossSwap.outputToken.symbol} ` + + `${originSwapChainId} -> ${crossSwap.outputToken.chainId}` + ); + } + + const _bridgeableInputToken = getTokenByAddress( + bridgeRoute.fromTokenAddress, + bridgeRoute.fromChain + ); + + if (!_bridgeableInputToken) { + throw new Error( + `No bridgeable input token found for ${bridgeRoute.toTokenAddress} on chain ${bridgeRoute.toChain}` + ); + } + + const bridgeableInputToken = { + address: bridgeRoute.fromTokenAddress, + decimals: _bridgeableInputToken.decimals, + symbol: _bridgeableInputToken.symbol, + chainId: bridgeRoute.fromChain, + }; + + // 1. Get bridge quote for bridgeable input token -> bridgeable output token + const bridgeQuote = await profiler.measureAsync( + getBridgeQuoteForMinOutput({ + inputToken: bridgeableInputToken, + outputToken: crossSwap.outputToken, + minOutputAmount: crossSwap.amount, + recipient: getMultiCallHandlerAddress(destinationChainId), + message: buildExactOutputBridgeTokenMessage(crossSwap), + }), + "getBridgeQuote" + ); + // 1.1. Update bridge quote message for min. output amount + if (crossSwap.type === AMOUNT_TYPE.MIN_OUTPUT && crossSwap.isOutputNative) { + bridgeQuote.message = buildMinOutputBridgeTokenMessage( + crossSwap, + bridgeQuote.outputAmount + ); + } + + const originSwapEntryPoint = + originStrategy.getOriginEntryPoints(originSwapChainId).swapAndBridge; + const originSwap = { + chainId: originSwapChainId, + tokenIn: crossSwap.inputToken, + tokenOut: bridgeableInputToken, + recipient: originSwapEntryPoint.address, + slippageTolerance: crossSwap.slippageTolerance, + type: crossSwap.type, + }; + // 2.1. Get origin swap quote for any input token -> bridgeable input token + const originSwapQuote = await profiler.measureAsync( + originStrategy.fetchFn( + { + ...originSwap, + amount: bridgeQuote.inputAmount.toString(), + }, + TradeType.EXACT_OUTPUT, + { + useIndicativeQuote: true, + } + ), + "INDICATIVE_getOriginSwapQuote" + ); + // 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( + originStrategy.fetchFn( + { + ...originSwap, + amount: addMarkupToAmount( + originSwapQuote.maximumAmountIn, + indicativeQuoteBuffer + ).toString(), + }, + TradeType.EXACT_INPUT + ), + "getOriginSwapQuote" + ); + assertMinOutputAmount( + adjOriginSwapQuote.minAmountOut, + bridgeQuote.inputAmount + ); + + return { + crossSwap, + bridgeQuote, + destinationSwapQuote: undefined, + originSwapQuote, + contracts: { + originSwapEntryPoint, + depositEntryPoint: + originStrategy.getOriginEntryPoints(originSwapChainId).deposit, + originRouter: originStrategy.getRouter(originSwapChainId), + }, + }; +} + +export async function getCrossSwapQuotesForOutputA2A( + crossSwap: CrossSwap, + 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 + ); + + if (allBridgeRoutes.length === 0) { + throw new Error( + `No bridge routes found for ${originSwapChainId} -> ${destinationSwapChainId}` + ); + } + + const preferredBridgeRoutes = allBridgeRoutes.filter(({ toTokenSymbol }) => + preferredBridgeTokens.includes(toTokenSymbol) + ); + const bridgeRoutesToCompare = ( + preferredBridgeRoutes.length > 0 ? preferredBridgeRoutes : allBridgeRoutes + ).slice(0, bridgeRoutesLimit); + + if (bridgeRoutesToCompare.length === 0) { + throw new Error( + `No bridge routes to compare for ${originSwapChainId} -> ${destinationSwapChainId}` + ); + } + + const crossSwapQuotes = await Promise.all( + bridgeRoutesToCompare.map((bridgeRoute) => + getCrossSwapQuotesForOutputByRouteA2A( + crossSwap, + bridgeRoute, + originStrategy, + destinationStrategy + ) + ) + ); + + // Compare quotes by lowest input amount + const bestCrossSwapQuote = crossSwapQuotes.reduce((prev, curr) => + prev.originSwapQuote!.maximumAmountIn.lt( + curr.originSwapQuote!.maximumAmountIn + ) + ? prev + : curr + ); + return bestCrossSwapQuote; +} + +export async function getCrossSwapQuotesForOutputByRouteA2A( + crossSwap: CrossSwap, + bridgeRoute: { + fromTokenAddress: string; + fromChain: number; + toTokenAddress: string; + toChain: number; + }, + originStrategy: QuoteFetchStrategy, + destinationStrategy: QuoteFetchStrategy +): Promise { + const profiler = new Profiler({ + at: "api/_dexes/cross-swap-service#getCrossSwapQuotesForOutputByRouteA2A", + logger: console, + }); + const originSwapChainId = crossSwap.inputToken.chainId; + const destinationSwapChainId = crossSwap.outputToken.chainId; + + const _bridgeableInputToken = getTokenByAddress( + bridgeRoute.fromTokenAddress, + bridgeRoute.fromChain + ); + const _bridgeableOutputToken = getTokenByAddress( + bridgeRoute.toTokenAddress, + bridgeRoute.toChain + ); + + if (!_bridgeableInputToken) { + throw new Error( + `No bridgeable input token found for ${bridgeRoute.fromTokenAddress} on chain ${bridgeRoute.fromChain}` + ); + } + + if (!_bridgeableOutputToken) { + throw new Error( + `No bridgeable output token found for ${bridgeRoute.toTokenAddress} on chain ${bridgeRoute.toChain}` + ); + } + + const bridgeableInputToken = { + address: bridgeRoute.fromTokenAddress, + decimals: _bridgeableInputToken.decimals, + symbol: _bridgeableInputToken.symbol, + chainId: bridgeRoute.fromChain, + }; + const bridgeableOutputToken = { + address: bridgeRoute.toTokenAddress, + decimals: _bridgeableOutputToken.decimals, + symbol: _bridgeableOutputToken.symbol, + chainId: bridgeRoute.toChain, + }; + const multiCallHandlerAddress = getMultiCallHandlerAddress( + destinationSwapChainId + ); + const originSwapEntryPoint = + 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, + tokenOut: bridgeableInputToken, + recipient: originSwapEntryPoint.address, + slippageTolerance: crossSwap.slippageTolerance, + type: crossSwap.type, + }; + const destinationSwap = { + chainId: destinationSwapChainId, + tokenIn: bridgeableOutputToken, + tokenOut: crossSwap.outputToken, + recipient: multiCallHandlerAddress, + slippageTolerance: crossSwap.slippageTolerance, + type: crossSwap.type, + }; + + // Fetch INDICATIVE quotes sequentially: + // 1. Destination swap quote for bridgeable output token -> any token + // 2. Bridge quote for bridgeable input token -> bridgeable output token + // 3. Origin swap quote for any input token -> bridgeable input token + // These requests are faster but do not contain calldata. + const indicativeDestinationSwapQuote = await profiler.measureAsync( + destinationStrategy.fetchFn( + { + ...destinationSwap, + amount: crossSwap.amount.toString(), + }, + TradeType.EXACT_OUTPUT, + { + useIndicativeQuote: true, + } + ), + "INDICATIVE_getDestinationSwapQuote" + ); + const indicativeBridgeQuote = await profiler.measureAsync( + getBridgeQuoteForMinOutput({ + inputToken: bridgeableInputToken, + outputToken: bridgeableOutputToken, + minOutputAmount: addMarkupToAmount( + indicativeDestinationSwapQuote.maximumAmountIn, + indicativeQuoteBuffer + ), + recipient: getMultiCallHandlerAddress(destinationSwapChainId), + message: buildDestinationSwapCrossChainMessage({ + crossSwap, + destinationSwapQuote: indicativeDestinationSwapQuote, + bridgeableOutputToken, + routerAddress: destinationRouter.address, + }), + }), + "INDICATIVE_getBridgeQuote" + ); + const indicativeOriginSwapQuote = await profiler.measureAsync( + originStrategy.fetchFn( + { + ...originSwap, + amount: addMarkupToAmount( + indicativeBridgeQuote.inputAmount, + indicativeQuoteBuffer + ).toString(), + }, + TradeType.EXACT_OUTPUT, + { + useIndicativeQuote: true, + } + ), + "INDICATIVE_getOriginSwapQuote" + ); + + // Fetch REAL quotes in parallel. These requests are slower but contain calldata. + const [destinationSwapQuote, bridgeQuote, originSwapQuote] = + await profiler.measureAsync( + Promise.all([ + destinationStrategy.fetchFn( + { + ...destinationSwap, + amount: crossSwap.amount.toString(), + }, + TradeType.EXACT_OUTPUT + ), + getBridgeQuoteForMinOutput({ + inputToken: bridgeableInputToken, + outputToken: bridgeableOutputToken, + minOutputAmount: indicativeDestinationSwapQuote.maximumAmountIn, + recipient: getMultiCallHandlerAddress(destinationSwapChainId), + message: buildDestinationSwapCrossChainMessage({ + crossSwap, + destinationSwapQuote: indicativeDestinationSwapQuote, + bridgeableOutputToken, + routerAddress: destinationRouter.address, + }), + }), + originStrategy.fetchFn( + { + ...originSwap, + amount: addMarkupToAmount( + indicativeOriginSwapQuote.maximumAmountIn, + indicativeQuoteBuffer + ).toString(), + }, + TradeType.EXACT_INPUT + ), + ]), + "getAllQuotes" + ); + assertMinOutputAmount(destinationSwapQuote.minAmountOut, crossSwap.amount); + assertMinOutputAmount( + bridgeQuote.outputAmount, + destinationSwapQuote.maximumAmountIn + ); + assertMinOutputAmount(originSwapQuote.minAmountOut, bridgeQuote.inputAmount); + + bridgeQuote.message = buildDestinationSwapCrossChainMessage({ + crossSwap, + destinationSwapQuote, + bridgeableOutputToken, + routerAddress: destinationRouter.address, + }); + + return { + crossSwap, + destinationSwapQuote, + bridgeQuote, + originSwapQuote, + contracts: { + originSwapEntryPoint, + depositEntryPoint, + originRouter: originRouter, + destinationRouter: destinationRouter, + }, + }; +} diff --git a/api/_dexes/types.ts b/api/_dexes/types.ts index 84e17a905..890bcac60 100644 --- a/api/_dexes/types.ts +++ b/api/_dexes/types.ts @@ -1,3 +1,11 @@ +import { BigNumber } from "ethers"; +import { TradeType } from "@uniswap/sdk-core"; + +import { getSuggestedFees } from "../_utils"; +import { AmountType, CrossSwapType } from "./utils"; + +export type { AmountType, CrossSwapType }; + export type Token = { address: string; decimals: number; @@ -5,22 +13,35 @@ export type Token = { chainId: number; }; -/** - * @property `swapToken` - Address of the token that will be swapped for acrossInputToken. - * @property `acrossInputToken` - Address of the token that will be bridged via Across as the inputToken. - * @property `swapTokenAmount` - The amount of swapToken to be swapped for acrossInputToken. - * @property `slippage` - The slippage tolerance for the swap in decimals, e.g. 1 for 1%. - */ -export type AcrossSwap = { - swapToken: Token; - acrossInputToken: Token; - swapTokenAmount: string; - slippage: number; +export type Swap = { + chainId: number; + tokenIn: Token; + tokenOut: Token; + amount: string; + recipient: string; + slippageTolerance: number; + type: AmountType; + isInputNative?: boolean; + isOutputNative?: boolean; +}; + +export type CrossSwap = { + amount: BigNumber; + inputToken: Token; + outputToken: Token; + depositor: string; + recipient: string; + slippageTolerance: number; + type: AmountType; + refundOnOrigin: boolean; + refundAddress?: string; + isInputNative?: boolean; + isOutputNative?: boolean; }; export type SupportedDex = "1inch" | "uniswap"; -export type SwapQuoteAndCalldata = { +export type OriginSwapQuoteAndCalldata = { minExpectedInputTokenAmount: string; routerCalldata: string; value: string; @@ -28,3 +49,100 @@ export type SwapQuoteAndCalldata = { dex: SupportedDex; slippage: number; }; + +export type SwapQuote = { + maximumAmountIn: BigNumber; + minAmountOut: BigNumber; + expectedAmountOut: BigNumber; + expectedAmountIn: BigNumber; + slippageTolerance: number; + swapTx: { + to: string; + data: string; + value: string; + }; + tokenIn: Token; + tokenOut: Token; +}; + +export type CrossSwapQuotes = { + crossSwap: CrossSwap; + bridgeQuote: { + message?: string; + inputToken: Token; + outputToken: Token; + inputAmount: BigNumber; + outputAmount: BigNumber; + minOutputAmount: BigNumber; + suggestedFees: Awaited>; + }; + destinationSwapQuote?: SwapQuote; + originSwapQuote?: SwapQuote; + contracts: { + depositEntryPoint: DepositEntryPointContract; + originRouter?: RouterContract; + destinationRouter?: RouterContract; + originSwapEntryPoint?: OriginSwapEntryPointContract; + }; +}; + +export type OriginSwapEntryPointContract = + | { + name: "SpokePoolPeripheryProxy" | "SpokePoolPeriphery"; + address: string; + } + | { + name: "UniversalSwapAndBridge"; + 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; +}; + +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/adapter.ts b/api/_dexes/uniswap/adapter.ts new file mode 100644 index 000000000..3b686ad84 --- /dev/null +++ b/api/_dexes/uniswap/adapter.ts @@ -0,0 +1,252 @@ +import { MixedRouteSDK, Trade as RouterTrade } from "@uniswap/router-sdk"; +import { + Currency, + CurrencyAmount, + Ether, + Token, + TradeType, +} from "@uniswap/sdk-core"; +import { Pair, Route as V2Route } from "@uniswap/v2-sdk"; +import { Pool, Route as V3Route, FeeAmount } from "@uniswap/v3-sdk"; +import { BigNumber } from "ethers"; + +export type TokenInRoute = { + address: string; + chainId: number; + symbol: string; + decimals: string; + name?: string; + buyFeeBps?: string; + sellFeeBps?: string; +}; + +export enum PoolType { + V2Pool = "v2-pool", + V3Pool = "v3-pool", + V4Pool = "v4-pool", +} + +export type V2Reserve = { + token: TokenInRoute; + quotient: string; +}; + +export type V2PoolInRoute = { + type: PoolType.V2Pool; + address?: string; + tokenIn: TokenInRoute; + tokenOut: TokenInRoute; + reserve0: V2Reserve; + reserve1: V2Reserve; + amountIn?: string; + amountOut?: string; +}; + +export type V3PoolInRoute = { + type: PoolType.V3Pool; + address?: string; + tokenIn: TokenInRoute; + tokenOut: TokenInRoute; + sqrtRatioX96: string; + liquidity: string; + tickCurrent: string; + fee: string; + amountIn?: string; + amountOut?: string; +}; + +export type PartialClassicQuote = { + // We need tokenIn/Out to support native currency + tokenIn: string; + tokenOut: string; + tradeType: TradeType; + route: Array<(V3PoolInRoute | V2PoolInRoute)[]>; +}; + +interface RouteResult { + routev3: V3Route | null; + routev2: V2Route | null; + mixedRoute: MixedRouteSDK | null; + inputAmount: CurrencyAmount; + outputAmount: CurrencyAmount; +} + +// Helper class to convert routing-specific quote entities to RouterTrade entities +// the returned RouterTrade can then be used to build the UniswapTrade entity in this package +export class RouterTradeAdapter { + // Generate a RouterTrade using fields from a classic quote response + static fromClassicQuote(quote: PartialClassicQuote) { + const { route } = quote; + + if (!route) throw new Error("Expected route to be present"); + if (!route.length) + throw new Error("Expected there to be at least one route"); + if (route.some((r) => !r.length)) + throw new Error("Expected all routes to have at least one pool"); + const firstRoute = route[0]; + + const tokenInData = firstRoute[0].tokenIn; + const tokenOutData = firstRoute[firstRoute.length - 1].tokenOut; + + if (!tokenInData || !tokenOutData) + throw new Error("Expected both tokenIn and tokenOut to be present"); + if (tokenInData.chainId !== tokenOutData.chainId) + throw new Error("Expected tokenIn and tokenOut to be have same chainId"); + + const parsedCurrencyIn = RouterTradeAdapter.toCurrency(false, tokenInData); + const parsedCurrencyOut = RouterTradeAdapter.toCurrency( + false, + tokenOutData + ); + + const typedRoutes: RouteResult[] = route.map((subRoute) => { + const rawAmountIn = subRoute[0].amountIn; + const rawAmountOut = subRoute[subRoute.length - 1].amountOut; + + if (!rawAmountIn || !rawAmountOut) { + throw new Error( + "Expected both raw amountIn and raw amountOut to be present" + ); + } + + const inputAmount = CurrencyAmount.fromRawAmount( + parsedCurrencyIn, + rawAmountIn + ); + const outputAmount = CurrencyAmount.fromRawAmount( + parsedCurrencyOut, + rawAmountOut + ); + + const isOnlyV2 = RouterTradeAdapter.isVersionedRoute( + PoolType.V2Pool, + subRoute + ); + const isOnlyV3 = RouterTradeAdapter.isVersionedRoute( + PoolType.V3Pool, + subRoute + ); + + return { + routev3: isOnlyV3 + ? new V3Route( + (subRoute as V3PoolInRoute[]).map(RouterTradeAdapter.toPool), + parsedCurrencyIn, + parsedCurrencyOut + ) + : null, + routev2: isOnlyV2 + ? new V2Route( + (subRoute as V2PoolInRoute[]).map(RouterTradeAdapter.toPair), + parsedCurrencyIn, + parsedCurrencyOut + ) + : null, + mixedRoute: + !isOnlyV3 && !isOnlyV2 + ? new MixedRouteSDK( + subRoute.map(RouterTradeAdapter.toPoolOrPair), + parsedCurrencyIn, + parsedCurrencyOut + ) + : null, + inputAmount, + outputAmount, + }; + }); + + return new RouterTrade({ + v2Routes: typedRoutes + .filter((route) => route.routev2) + .map((route) => ({ + routev2: route.routev2 as V2Route, + inputAmount: route.inputAmount, + outputAmount: route.outputAmount, + })), + v3Routes: typedRoutes + .filter((route) => route.routev3) + .map((route) => ({ + routev3: route.routev3 as V3Route, + inputAmount: route.inputAmount, + outputAmount: route.outputAmount, + })), + // TODO: ROUTE-219 - Support v4 trade in universal-router sdk + v4Routes: [], + mixedRoutes: typedRoutes + .filter((route) => route.mixedRoute) + .map((route) => ({ + mixedRoute: route.mixedRoute as MixedRouteSDK, + inputAmount: route.inputAmount, + outputAmount: route.outputAmount, + })), + tradeType: quote.tradeType, + }); + } + + private static toCurrency(isNative: boolean, token: TokenInRoute): Currency { + if (isNative) { + return Ether.onChain(token.chainId); + } + return this.toToken(token); + } + + private static toPoolOrPair = ( + pool: V3PoolInRoute | V2PoolInRoute + ): Pool | Pair => { + return pool.type === PoolType.V3Pool + ? RouterTradeAdapter.toPool(pool) + : RouterTradeAdapter.toPair(pool); + }; + + private static toToken(token: TokenInRoute): Token { + const { chainId, address, decimals, symbol, buyFeeBps, sellFeeBps } = token; + return new Token( + chainId, + address, + parseInt(decimals.toString()), + symbol, + /* name */ undefined, + false, + buyFeeBps ? BigNumber.from(buyFeeBps) : undefined, + sellFeeBps ? BigNumber.from(sellFeeBps) : undefined + ); + } + + private static toPool({ + fee, + sqrtRatioX96, + liquidity, + tickCurrent, + tokenIn, + tokenOut, + }: V3PoolInRoute): Pool { + return new Pool( + RouterTradeAdapter.toToken(tokenIn), + RouterTradeAdapter.toToken(tokenOut), + parseInt(fee) as FeeAmount, + sqrtRatioX96, + liquidity, + parseInt(tickCurrent) + ); + } + + private static toPair = ({ reserve0, reserve1 }: V2PoolInRoute): Pair => { + return new Pair( + CurrencyAmount.fromRawAmount( + RouterTradeAdapter.toToken(reserve0.token), + reserve0.quotient + ), + CurrencyAmount.fromRawAmount( + RouterTradeAdapter.toToken(reserve1.token), + reserve1.quotient + ) + ); + }; + + private static isVersionedRoute( + type: PoolType, + route: (V3PoolInRoute | V2PoolInRoute)[] + ): route is T[] { + return route.every((pool) => pool.type === type); + } +} diff --git a/api/_dexes/uniswap.ts b/api/_dexes/uniswap/swap-quoter.ts similarity index 62% rename from api/_dexes/uniswap.ts rename to api/_dexes/uniswap/swap-quoter.ts index d693dc5a4..7d3458a3e 100644 --- a/api/_dexes/uniswap.ts +++ b/api/_dexes/uniswap/swap-quoter.ts @@ -21,14 +21,10 @@ import IUniswapV3PoolABI from "@uniswap/v3-core/artifacts/contracts/interfaces/I import { CHAIN_IDs } from "@across-protocol/constants"; import { utils } from "@across-protocol/sdk"; -import { callViaMulticall3, getProvider } from "../_utils"; -import { TOKEN_SYMBOLS_MAP } from "../_constants"; -import { - AcrossSwap, - SwapQuoteAndCalldata, - Token as AcrossToken, -} from "./types"; -import { getSwapAndBridgeAddress } from "./utils"; +import { Swap } from "../types"; +import { getSwapAndBridgeAddress } from "../../_swap-and-bridge"; +import { getProdToken } from "./utils"; +import { callViaMulticall3, getProvider } from "../../_utils"; // https://docs.uniswap.org/contracts/v3/reference/deployments/ const POOL_FACTORY_CONTRACT_ADDRESS = { @@ -46,40 +42,32 @@ const QUOTER_CONTRACT_ADDRESS = { [CHAIN_IDs.POLYGON]: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e", }; -// Maps testnet chain IDs to their main counterparts. Used to get the mainnet token -// info for testnet tokens. -const TESTNET_TO_MAINNET = { - [CHAIN_IDs.SEPOLIA]: CHAIN_IDs.MAINNET, - [CHAIN_IDs.BASE_SEPOLIA]: CHAIN_IDs.BASE, - [CHAIN_IDs.OPTIMISM_SEPOLIA]: CHAIN_IDs.OPTIMISM, - [CHAIN_IDs.ARBITRUM_SEPOLIA]: CHAIN_IDs.ARBITRUM, -}; +type SwapParam = Omit; -export async function getUniswapQuoteAndCalldata( - swap: AcrossSwap -): Promise { +export async function getUniswapQuoteWithSwapQuoter(swap: SwapParam) { const swapAndBridgeAddress = getSwapAndBridgeAddress( "uniswap", - swap.swapToken.chainId + swap.tokenIn.chainId ); - const initialSwapToken = { ...swap.swapToken }; - const initialAcrossInputToken = { ...swap.acrossInputToken }; + const initialTokenIn = { ...swap.tokenIn }; + const initialTokenOut = { ...swap.tokenOut }; + // Always use mainnet tokens for retrieving quote, so that we can get equivalent quotes // for testnet tokens. - swap.swapToken = getMainnetToken(swap.swapToken); - swap.acrossInputToken = getMainnetToken(swap.acrossInputToken); + swap.tokenIn = getProdToken(swap.tokenIn); + swap.tokenOut = getProdToken(swap.tokenOut); const poolInfo = await getPoolInfo(swap); const tokenA = new Token( - swap.swapToken.chainId, - swap.swapToken.address, - swap.swapToken.decimals + swap.tokenIn.chainId, + swap.tokenIn.address, + swap.tokenIn.decimals ); const tokenB = new Token( - swap.acrossInputToken.chainId, - swap.acrossInputToken.address, - swap.acrossInputToken.decimals + swap.tokenOut.chainId, + swap.tokenOut.address, + swap.tokenOut.decimals ); const pool = new Pool( tokenA, @@ -96,15 +84,16 @@ export async function getUniswapQuoteAndCalldata( const uncheckedTrade = Trade.createUncheckedTrade({ route: swapRoute, - inputAmount: CurrencyAmount.fromRawAmount(tokenA, swap.swapTokenAmount), + inputAmount: CurrencyAmount.fromRawAmount(tokenA, swap.amount), outputAmount: CurrencyAmount.fromRawAmount(tokenB, JSBI.BigInt(amountOut)), + // @TODO: Support other trade types tradeType: TradeType.EXACT_INPUT, }); const options: SwapOptions = { slippageTolerance: new Percent( // max. slippage decimals is 2 - Number(swap.slippage.toFixed(2)) * 100, + Number(swap.slippageTolerance.toFixed(2)) * 100, 10_000 ), // 20 minutes from the current Unix time @@ -119,61 +108,57 @@ export async function getUniswapQuoteAndCalldata( // replace mainnet token addresses with initial token addresses in calldata methodParameters.calldata = methodParameters.calldata.replace( - swap.swapToken.address.toLowerCase().slice(2), - initialSwapToken.address.toLowerCase().slice(2) + swap.tokenIn.address.toLowerCase().slice(2), + initialTokenIn.address.toLowerCase().slice(2) ); methodParameters.calldata = methodParameters.calldata.replace( - swap.acrossInputToken.address.toLowerCase().slice(2), - initialAcrossInputToken.address.toLowerCase().slice(2) + swap.tokenOut.address.toLowerCase().slice(2), + initialTokenOut.address.toLowerCase().slice(2) ); return { minExpectedInputTokenAmount: ethers.utils .parseUnits( uncheckedTrade.minimumAmountOut(options.slippageTolerance).toExact(), - swap.acrossInputToken.decimals + swap.tokenOut.decimals ) .toString(), routerCalldata: methodParameters.calldata, value: ethers.BigNumber.from(methodParameters.value).toString(), swapAndBridgeAddress, dex: "uniswap", - slippage: swap.slippage, + slippage: swap.slippageTolerance, }; } async function getOutputQuote( - swap: AcrossSwap, + swap: SwapParam, route: Route ) { const provider = getProvider(route.chainId); - const { calldata } = SwapQuoter.quoteCallParameters( route, CurrencyAmount.fromRawAmount( new Token( - swap.swapToken.chainId, - swap.swapToken.address, - swap.swapToken.decimals + swap.tokenIn.chainId, + swap.tokenIn.address, + swap.tokenIn.decimals ), - swap.swapTokenAmount + swap.amount ), + // @TODO: Support other trade types TradeType.EXACT_INPUT, { useQuoterV2: true } ); - const quoteCallReturnData = await provider.call({ - to: QUOTER_CONTRACT_ADDRESS[swap.swapToken.chainId], + to: QUOTER_CONTRACT_ADDRESS[swap.tokenIn.chainId], data: calldata, }); return ethers.utils.defaultAbiCoder.decode(["uint256"], quoteCallReturnData); } -async function getPoolInfo({ - swapToken, - acrossInputToken, -}: AcrossSwap): Promise<{ +async function getPoolInfo({ tokenIn, tokenOut }: SwapParam): Promise<{ token0: string; token1: string; fee: number; @@ -182,26 +167,17 @@ async function getPoolInfo({ liquidity: ethers.BigNumber; tick: number; }> { - const provider = getProvider(swapToken.chainId); + const provider = getProvider(tokenIn.chainId); const poolContract = new ethers.Contract( computePoolAddress({ - factoryAddress: POOL_FACTORY_CONTRACT_ADDRESS[swapToken.chainId], - tokenA: new Token( - swapToken.chainId, - swapToken.address, - swapToken.decimals - ), - tokenB: new Token( - acrossInputToken.chainId, - acrossInputToken.address, - acrossInputToken.decimals - ), + factoryAddress: POOL_FACTORY_CONTRACT_ADDRESS[tokenIn.chainId], + tokenA: new Token(tokenIn.chainId, tokenIn.address, tokenIn.decimals), + tokenB: new Token(tokenOut.chainId, tokenOut.address, tokenOut.decimals), fee: FeeAmount.LOW, }), IUniswapV3PoolABI.abi, provider ); - const poolMethods = [ "token0", "token1", @@ -237,23 +213,3 @@ async function getPoolInfo({ tick: number; }; } - -function getMainnetToken(token: AcrossToken) { - const mainnetChainId = TESTNET_TO_MAINNET[token.chainId] || token.chainId; - - const mainnetToken = - TOKEN_SYMBOLS_MAP[token.symbol as keyof typeof TOKEN_SYMBOLS_MAP]; - const mainnetTokenAddress = mainnetToken?.addresses[mainnetChainId]; - - if (!mainnetToken || !mainnetTokenAddress) { - throw new Error( - `Mainnet token not found for ${token.symbol} on chain ${token.chainId}` - ); - } - - return { - ...mainnetToken, - chainId: mainnetChainId, - address: mainnetTokenAddress, - }; -} diff --git a/api/_dexes/uniswap/swap-router-02.ts b/api/_dexes/uniswap/swap-router-02.ts new file mode 100644 index 000000000..534cd52ae --- /dev/null +++ b/api/_dexes/uniswap/swap-router-02.ts @@ -0,0 +1,262 @@ +import { BigNumber, ethers } from "ethers"; +import { TradeType } from "@uniswap/sdk-core"; +import { SwapRouter } from "@uniswap/router-sdk"; + +import { CHAIN_IDs } from "@across-protocol/constants"; + +import { + getLogger, + getSpokePoolAddress, + addMarkupToAmount, +} 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 { buildCacheKey, makeCacheGetterAndSetter } from "../../_cache"; + +// Taken from here: https://docs.uniswap.org/contracts/v3/reference/deployments/ +export const SWAP_ROUTER_02_ADDRESS = { + [CHAIN_IDs.ARBITRUM]: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", + [CHAIN_IDs.BASE]: "0x2626664c2603336E57B271c5C0b26F421741e481", + [CHAIN_IDs.BLAST]: "0x549FEB8c9bd4c12Ad2AB27022dA12492aC452B66", + [CHAIN_IDs.MAINNET]: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", + [CHAIN_IDs.OPTIMISM]: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", + [CHAIN_IDs.POLYGON]: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", + [CHAIN_IDs.WORLD_CHAIN]: "0x091AD9e2e6e5eD44c1c66dB50e49A601F9f36cF6", + [CHAIN_IDs.ZK_SYNC]: "0x99c56385daBCE3E81d8499d0b8d0257aBC07E8A3", + [CHAIN_IDs.ZORA]: "0x7De04c96BE5159c3b5CeffC82aa176dc81281557", +}; + +export function getSwapRouter02Strategy( + originSwapEntryPointContractName: + | "SpokePoolPeriphery" + | "SpokePoolPeripheryProxy" + | "UniversalSwapAndBridge" +): UniswapQuoteFetchStrategy { + 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 { + swapAndBridge: { + name: "SpokePoolPeriphery", + address: getSpokePoolPeripheryAddress(chainId), + }, + deposit: { + name: "SpokePoolPeriphery", + address: getSpokePoolPeripheryAddress(chainId), + }, + } as const; + } else if (originSwapEntryPointContractName === "UniversalSwapAndBridge") { + return { + swapAndBridge: { + name: "UniversalSwapAndBridge", + address: getUniversalSwapAndBridgeAddress("uniswap", chainId), + dex: "uniswap", + }, + deposit: { + name: "SpokePool", + address: getSpokePoolAddress(chainId), + }, + } as const; + } + throw new Error( + `Unknown origin swap entry point contract '${originSwapEntryPointContractName}'` + ); + }; + + const fetchFn = async ( + swap: Swap, + tradeType: TradeType, + opts: Partial<{ + useIndicativeQuote: boolean; + }> = { + useIndicativeQuote: false, + } + ) => { + let swapQuote: SwapQuote; + if (!opts.useIndicativeQuote) { + const { quote } = await getUniswapClassicQuoteFromApi( + { ...swap, swapper: swap.recipient }, + tradeType + ); + const swapTx = buildSwapRouterSwapTx(swap, tradeType, quote); + + const expectedAmountIn = BigNumber.from(quote.input.amount); + const maxAmountIn = + tradeType === TradeType.EXACT_INPUT + ? expectedAmountIn + : addMarkupToAmount(expectedAmountIn, quote.slippage / 100); + const expectedAmountOut = BigNumber.from(quote.output.amount); + const minAmountOut = + tradeType === TradeType.EXACT_OUTPUT + ? expectedAmountOut + : addMarkupToAmount(expectedAmountOut, -quote.slippage / 100); + + swapQuote = { + tokenIn: swap.tokenIn, + tokenOut: swap.tokenOut, + maximumAmountIn: maxAmountIn, + minAmountOut, + expectedAmountOut, + expectedAmountIn, + slippageTolerance: quote.slippage, + swapTx, + }; + } else { + const indicativeQuotePricePerTokenOut = await indicativeQuotePriceCache( + swap, + tradeType + ).get(); + const inputAmount = + tradeType === TradeType.EXACT_INPUT + ? swap.amount + : ethers.utils.parseUnits( + ( + Number( + ethers.utils.formatUnits(swap.amount, swap.tokenOut.decimals) + ) * indicativeQuotePricePerTokenOut + ).toFixed(swap.tokenIn.decimals), + swap.tokenIn.decimals + ); + const outputAmount = + tradeType === TradeType.EXACT_INPUT + ? ethers.utils.parseUnits( + ( + Number( + ethers.utils.formatUnits(swap.amount, swap.tokenIn.decimals) + ) / indicativeQuotePricePerTokenOut + ).toFixed(swap.tokenOut.decimals), + swap.tokenOut.decimals + ) + : swap.amount; + + const expectedAmountIn = BigNumber.from(inputAmount); + const maxAmountIn = + tradeType === TradeType.EXACT_INPUT + ? expectedAmountIn + : addMarkupToAmount(expectedAmountIn, swap.slippageTolerance / 100); + const expectedAmountOut = BigNumber.from(outputAmount); + const minAmountOut = + tradeType === TradeType.EXACT_OUTPUT + ? expectedAmountOut + : addMarkupToAmount(expectedAmountOut, -swap.slippageTolerance / 100); + + swapQuote = { + tokenIn: swap.tokenIn, + tokenOut: swap.tokenOut, + maximumAmountIn: maxAmountIn, + minAmountOut, + expectedAmountOut, + expectedAmountIn, + slippageTolerance: swap.slippageTolerance, + swapTx: { + to: "0x0", + data: "0x0", + value: "0x0", + }, + }; + } + + getLogger().debug({ + at: "uniswap/swap-router-02/fetchFn", + message: "Swap quote", + type: + tradeType === TradeType.EXACT_INPUT ? "EXACT_INPUT" : "EXACT_OUTPUT", + tokenIn: swapQuote.tokenIn.symbol, + tokenOut: swapQuote.tokenOut.symbol, + chainId: swap.chainId, + maximumAmountIn: swapQuote.maximumAmountIn.toString(), + minAmountOut: swapQuote.minAmountOut.toString(), + expectedAmountOut: swapQuote.expectedAmountOut.toString(), + expectedAmountIn: swapQuote.expectedAmountIn.toString(), + }); + + return swapQuote; + }; + + return { + getRouter, + getOriginEntryPoints, + fetchFn, + }; +} + +export function buildSwapRouterSwapTx( + swap: Swap, + tradeType: TradeType, + quote: UniswapClassicQuoteFromApi +) { + const options = { + recipient: swap.recipient, + slippageTolerance: floatToPercent(swap.slippageTolerance), + }; + + const routerTrade = RouterTradeAdapter.fromClassicQuote({ + tokenIn: quote.input.token, + tokenOut: quote.output.token, + tradeType, + route: quote.route, + }); + const { calldata, value } = SwapRouter.swapCallParameters( + routerTrade, + options + ); + return { + data: calldata, + value, + to: SWAP_ROUTER_02_ADDRESS[swap.chainId], + }; +} + +export function indicativeQuotePriceCache(swap: Swap, tradeType: TradeType) { + // TODO: Add price buckets based on USD value, e.g. 100, 1000, 10000 + const cacheKey = buildCacheKey( + "uniswap-indicative-quote", + tradeType === TradeType.EXACT_INPUT ? "EXACT_INPUT" : "EXACT_OUTPUT", + swap.chainId, + swap.tokenIn.symbol, + swap.tokenOut.symbol + ); + const ttl = 60; + const fetchFn = async () => { + const quote = await getUniswapClassicIndicativeQuoteFromApi( + { ...swap, swapper: swap.recipient }, + tradeType + ); + const pricePerTokenOut = + Number( + ethers.utils.formatUnits(quote.input.amount, swap.tokenIn.decimals) + ) / + Number( + ethers.utils.formatUnits(quote.output.amount, swap.tokenOut.decimals) + ); + return pricePerTokenOut; + }; + return makeCacheGetterAndSetter(cacheKey, ttl, fetchFn); +} diff --git a/api/_dexes/uniswap/trading-api.ts b/api/_dexes/uniswap/trading-api.ts new file mode 100644 index 000000000..8f556ab07 --- /dev/null +++ b/api/_dexes/uniswap/trading-api.ts @@ -0,0 +1,143 @@ +import { TradeType } from "@uniswap/sdk-core"; +import axios from "axios"; + +import { Swap } from "../types"; +import { V2PoolInRoute, V3PoolInRoute } from "./adapter"; + +export type UniswapClassicQuoteFromApi = { + chainId: number; + input: { + amount: string; + token: string; + }; + output: { + amount: string; + token: string; + recipient: string; + }; + swapper: string; + route: Array<(V3PoolInRoute | V2PoolInRoute)[]>; + slippage: number; + tradeType: "EXACT_OUTPUT" | "EXACT_INPUT"; + quoteId: string; +}; + +export type UniswapIndicativeQuoteFromApi = Awaited< + ReturnType +>; + +export type UniswapParamForApi = Omit & { + swapper: string; + slippageTolerance?: number; +}; + +export const UNISWAP_TRADING_API_BASE_URL = + process.env.UNISWAP_TRADING_API_BASE_URL || + "https://trading-api-labs.interface.gateway.uniswap.org/v1"; + +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: UniswapParamForApi, + tradeType: TradeType +) { + const response = await axios.post<{ + requestId: string; + routing: "CLASSIC"; + quote: UniswapClassicQuoteFromApi; + }>( + `${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, + autoSlippage: swap.slippageTolerance ? undefined : "DEFAULT", + amount: swap.amount, + urgency: "urgent", + routingPreference: "CLASSIC", + }, + { + headers: { + "x-api-key": UNISWAP_API_KEY, + }, + } + ); + return response.data; +} + +export async function getUniswapClassicIndicativeQuoteFromApi( + swap: UniswapParamForApi, + tradeType: TradeType +) { + const response = await axios.post<{ + requestId: string; + input: { + amount: string; + chainId: number; + token: string; + }; + output: { + amount: string; + chainId: number; + token: string; + }; + }>( + `${UNISWAP_TRADING_API_BASE_URL}/indicative_quote`, + { + type: + tradeType === TradeType.EXACT_INPUT ? "EXACT_INPUT" : "EXACT_OUTPUT", + amount: swap.amount, + tokenInChainId: swap.tokenIn.chainId, + tokenOutChainId: swap.tokenOut.chainId, + tokenIn: swap.tokenIn.address, + tokenOut: swap.tokenOut.address, + }, + { + headers: { + "x-api-key": UNISWAP_API_KEY, + }, + } + ); + return response.data; +} + +export async function getUniswapClassicCalldataFromApi( + classicQuote: UniswapClassicQuoteFromApi +) { + const response = await axios.post<{ + requestId: string; + swap: { + to: string; + from: string; + data: string; + value: string; + gasLimit: string; + chainId: number; + maxFeePerGas: string; + maxPriorityFeePerGas: string; + gasPrice: string; + }; + }>( + `${UNISWAP_TRADING_API_BASE_URL}/swap`, + { + quote: classicQuote, + simulateTransaction: false, + urgency: "urgent", + }, + { + headers: { + "x-api-key": UNISWAP_API_KEY, + }, + } + ); + return response.data; +} diff --git a/api/_dexes/uniswap/universal-router.ts b/api/_dexes/uniswap/universal-router.ts new file mode 100644 index 000000000..5e24376d4 --- /dev/null +++ b/api/_dexes/uniswap/universal-router.ts @@ -0,0 +1,172 @@ +import { BigNumber } from "ethers"; +import { TradeType } from "@uniswap/sdk-core"; +import { CHAIN_IDs } from "@across-protocol/constants"; +import { SwapRouter } from "@uniswap/universal-router-sdk"; + +import { getLogger, addMarkupToAmount } from "../../_utils"; +import { Swap, SwapQuote } from "../types"; +import { + getSpokePoolPeripheryAddress, + getSpokePoolPeripheryProxyAddress, +} from "../../_spoke-pool-periphery"; +import { + getUniswapClassicQuoteFromApi, + getUniswapClassicIndicativeQuoteFromApi, + UniswapClassicQuoteFromApi, +} from "./trading-api"; +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 +export const UNIVERSAL_ROUTER_ADDRESS = { + [CHAIN_IDs.ARBITRUM]: "0x5E325eDA8064b456f4781070C0738d849c824258", + [CHAIN_IDs.BASE]: "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD", + [CHAIN_IDs.BLAST]: "0x643770E279d5D0733F21d6DC03A8efbABf3255B4", + [CHAIN_IDs.MAINNET]: "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD", + [CHAIN_IDs.OPTIMISM]: "0xCb1355ff08Ab38bBCE60111F1bb2B784bE25D7e8", + [CHAIN_IDs.POLYGON]: "0xec7BE89e9d109e7e3Fec59c222CF297125FEFda2", + [CHAIN_IDs.WORLD_CHAIN]: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", + [CHAIN_IDs.ZORA]: "0x2986d9721A49838ab4297b695858aF7F17f38014", + [CHAIN_IDs.ZK_SYNC]: "0x28731BCC616B5f51dD52CF2e4dF0E78dD1136C06", +}; + +export function getUniversalRouterStrategy(): UniswapQuoteFetchStrategy { + const getRouter = (chainId: number) => { + return { + address: UNIVERSAL_ROUTER_ADDRESS[chainId], + name: "UniswapV3UniversalRouter", + }; + }; + + const getOriginEntryPoints = (chainId: number) => + ({ + swapAndBridge: { + name: "SpokePoolPeripheryProxy", + address: getSpokePoolPeripheryProxyAddress(chainId), + }, + deposit: { + name: "SpokePoolPeriphery", + address: getSpokePoolPeripheryAddress(chainId), + }, + }) as const; + + const fetchFn = async ( + swap: Swap, + tradeType: TradeType, + opts: Partial<{ + useIndicativeQuote: boolean; + }> = { + useIndicativeQuote: false, + } + ) => { + let swapQuote: SwapQuote; + if (!opts.useIndicativeQuote) { + const { quote } = await getUniswapClassicQuoteFromApi( + { ...swap, swapper: swap.recipient }, + tradeType + ); + const swapTx = buildUniversalRouterSwapTx(swap, tradeType, quote); + + const expectedAmountIn = BigNumber.from(quote.input.amount); + const maxAmountIn = + tradeType === TradeType.EXACT_INPUT + ? expectedAmountIn + : addMarkupToAmount(expectedAmountIn, quote.slippage / 100); + const expectedAmountOut = BigNumber.from(quote.output.amount); + const minAmountOut = + tradeType === TradeType.EXACT_OUTPUT + ? expectedAmountOut + : addMarkupToAmount(expectedAmountOut, -quote.slippage / 100); + + swapQuote = { + tokenIn: swap.tokenIn, + tokenOut: swap.tokenOut, + maximumAmountIn: maxAmountIn, + minAmountOut, + expectedAmountOut, + expectedAmountIn, + slippageTolerance: quote.slippage, + swapTx, + }; + } else { + const { input, output } = await getUniswapClassicIndicativeQuoteFromApi( + { ...swap, swapper: swap.recipient }, + tradeType + ); + + const expectedAmountIn = BigNumber.from(input.amount); + const maxAmountIn = + tradeType === TradeType.EXACT_INPUT + ? expectedAmountIn + : addMarkupToAmount(expectedAmountIn, swap.slippageTolerance / 100); + const expectedAmountOut = BigNumber.from(output.amount); + const minAmountOut = + tradeType === TradeType.EXACT_OUTPUT + ? expectedAmountOut + : addMarkupToAmount(expectedAmountOut, -swap.slippageTolerance / 100); + + swapQuote = { + tokenIn: swap.tokenIn, + tokenOut: swap.tokenOut, + maximumAmountIn: maxAmountIn, + minAmountOut, + expectedAmountOut, + expectedAmountIn, + slippageTolerance: swap.slippageTolerance, + swapTx: { + to: "0x", + data: "0x", + value: "0x", + }, + }; + } + + getLogger().debug({ + at: "uniswap/universal-router/fetchFn", + message: "Swap quote", + type: + tradeType === TradeType.EXACT_INPUT ? "EXACT_INPUT" : "EXACT_OUTPUT", + tokenIn: swapQuote.tokenIn.symbol, + tokenOut: swapQuote.tokenOut.symbol, + chainId: swap.chainId, + maximumAmountIn: swapQuote.maximumAmountIn.toString(), + minAmountOut: swapQuote.minAmountOut.toString(), + expectedAmountOut: swapQuote.expectedAmountOut.toString(), + expectedAmountIn: swapQuote.expectedAmountIn.toString(), + }); + + return swapQuote; + }; + + return { + getRouter, + getOriginEntryPoints, + fetchFn, + }; +} + +export function buildUniversalRouterSwapTx( + swap: Swap, + tradeType: TradeType, + quote: UniswapClassicQuoteFromApi +) { + const options = { + recipient: swap.recipient, + slippageTolerance: floatToPercent(swap.slippageTolerance), + }; + const routerTrade = RouterTradeAdapter.fromClassicQuote({ + tokenIn: quote.input.token, + tokenOut: quote.output.token, + tradeType, + route: quote.route, + }); + const { calldata, value } = SwapRouter.swapCallParameters( + routerTrade, + options + ); + return { + data: calldata, + value, + to: UNIVERSAL_ROUTER_ADDRESS[swap.chainId], + }; +} diff --git a/api/_dexes/uniswap/utils.ts b/api/_dexes/uniswap/utils.ts new file mode 100644 index 000000000..9699a7adc --- /dev/null +++ b/api/_dexes/uniswap/utils.ts @@ -0,0 +1,50 @@ +import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; +import { Percent } from "@uniswap/sdk-core"; + +import { Token, QuoteFetchStrategy } from "../types"; + +export type UniswapQuoteFetchStrategy = QuoteFetchStrategy; + +// Maps testnet chain IDs to their prod counterparts. Used to get the prod token +// info for testnet tokens. +const TESTNET_TO_PROD = { + [CHAIN_IDs.SEPOLIA]: CHAIN_IDs.MAINNET, + [CHAIN_IDs.BASE_SEPOLIA]: CHAIN_IDs.BASE, + [CHAIN_IDs.OPTIMISM_SEPOLIA]: CHAIN_IDs.OPTIMISM, + [CHAIN_IDs.ARBITRUM_SEPOLIA]: CHAIN_IDs.ARBITRUM, +}; + +export const UNISWAP_TRADING_API_BASE_URL = + process.env.UNISWAP_TRADING_API_BASE_URL || + "https://trading-api-labs.interface.gateway.uniswap.org/v1"; + +export const UNISWAP_API_KEY = + process.env.UNISWAP_API_KEY || "JoyCGj29tT4pymvhaGciK4r1aIPvqW6W53xT1fwo"; + +export function getProdToken(token: Token) { + const prodChainId = TESTNET_TO_PROD[token.chainId] || token.chainId; + + const prodToken = + TOKEN_SYMBOLS_MAP[token.symbol as keyof typeof TOKEN_SYMBOLS_MAP]; + const prodTokenAddress = prodToken?.addresses[prodChainId]; + + if (!prodToken || !prodTokenAddress) { + throw new Error( + `Prod token not found for ${token.symbol} on chain ${token.chainId}` + ); + } + + return { + ...prodToken, + chainId: prodChainId, + address: prodTokenAddress, + }; +} + +export function floatToPercent(value: number) { + return new Percent( + // max. slippage decimals is 2 + Number(value.toFixed(2)) * 100, + 10_000 + ); +} diff --git a/api/_dexes/utils.ts b/api/_dexes/utils.ts index 9edde40b9..1546c9e61 100644 --- a/api/_dexes/utils.ts +++ b/api/_dexes/utils.ts @@ -1,37 +1,418 @@ -import { ENABLED_ROUTES } from "../_utils"; +import { BigNumber, BigNumberish, constants } from "ethers"; +import { utils } from "@across-protocol/sdk"; +import { SpokePool } from "@across-protocol/contracts/dist/typechain"; -export class UnsupportedDex extends Error { - constructor(dex: string) { - super(`DEX/Aggregator ${dex} not supported`); +import { getSwapRouter02Strategy } from "./uniswap/swap-router-02"; +import { + buildMulticallHandlerMessage, + encodeApproveCalldata, + encodeDrainCalldata, + encodeTransferCalldata, + encodeWethWithdrawCalldata, + getMultiCallHandlerAddress, +} from "../_multicall-handler"; +import { + CrossSwap, + CrossSwapQuotes, + QuoteFetchStrategy, + SwapQuote, + Token, +} from "./types"; +import { + isInputTokenBridgeable, + isRouteEnabled, + isOutputTokenBridgeable, + getSpokePool, +} from "../_utils"; +import { GAS_SPONSOR_ADDRESS } from "../relay/_utils"; +import { SpokePoolV3PeripheryInterface } from "../_typechain/SpokePoolV3Periphery"; +import { TransferType } from "../_spoke-pool-periphery"; +export type CrossSwapType = + (typeof CROSS_SWAP_TYPE)[keyof typeof CROSS_SWAP_TYPE]; + +export type AmountType = (typeof AMOUNT_TYPE)[keyof typeof AMOUNT_TYPE]; + +export type QuoteFetchStrategies = Partial<{ + default: QuoteFetchStrategy; + [chainId: number]: QuoteFetchStrategy; +}>; + +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"]; + +export const defaultQuoteFetchStrategy: QuoteFetchStrategy = + // This will be our default strategy until the periphery contract is audited + getSwapRouter02Strategy("UniversalSwapAndBridge"); + +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; } -} -export class UnsupportedDexOnChain extends Error { - constructor(chainId: number, dex: string) { - super(`DEX/Aggregator ${dex} not supported on chain ${chainId}`); + if (isInputTokenBridgeable(params.inputToken, params.originChainId)) { + return CROSS_SWAP_TYPE.BRIDGEABLE_TO_ANY; } + + return CROSS_SWAP_TYPE.ANY_TO_ANY; +} + +/** + * This builds a cross-chain message for a (any/bridgeable)-to-bridgeable cross swap + * with a specific amount of output tokens that the recipient will receive. Excess + * tokens are refunded to the depositor. + */ +export function buildExactOutputBridgeTokenMessage(crossSwap: CrossSwap) { + const transferActions = crossSwap.isOutputNative + ? // WETH unwrap to ETH + [ + { + target: crossSwap.outputToken.address, + callData: encodeWethWithdrawCalldata(crossSwap.amount), + value: "0", + }, + { + target: crossSwap.recipient, + callData: "0x", + value: crossSwap.amount.toString(), + }, + ] + : // ERC-20 token transfer + [ + { + target: crossSwap.outputToken.address, + callData: encodeTransferCalldata( + crossSwap.recipient, + crossSwap.amount + ), + value: "0", + }, + ]; + return buildMulticallHandlerMessage({ + fallbackRecipient: getFallbackRecipient(crossSwap), + actions: [ + ...transferActions, + // drain remaining bridgeable output tokens from MultiCallHandler contract + { + target: getMultiCallHandlerAddress(crossSwap.outputToken.chainId), + callData: encodeDrainCalldata( + crossSwap.outputToken.address, + crossSwap.refundAddress ?? crossSwap.depositor + ), + value: "0", + }, + ], + }); +} + +/** + * This builds a cross-chain message for a (any/bridgeable)-to-bridgeable cross swap + * with a min. amount of output tokens that the recipient will receive. + */ +export function buildMinOutputBridgeTokenMessage( + crossSwap: CrossSwap, + unwrapAmount?: BigNumber +) { + const transferActions = crossSwap.isOutputNative + ? // WETH unwrap to ETH + [ + { + target: crossSwap.outputToken.address, + callData: encodeWethWithdrawCalldata( + unwrapAmount || crossSwap.amount + ), + value: "0", + }, + { + target: crossSwap.recipient, + callData: "0x", + value: (unwrapAmount || crossSwap.amount).toString(), + }, + ] + : // ERC-20 token transfer + []; + return buildMulticallHandlerMessage({ + fallbackRecipient: getFallbackRecipient(crossSwap), + actions: [ + ...transferActions, + // drain remaining bridgeable output tokens from MultiCallHandler contract + { + target: getMultiCallHandlerAddress(crossSwap.outputToken.chainId), + callData: encodeDrainCalldata( + crossSwap.outputToken.address, + crossSwap.recipient + ), + value: "0", + }, + ], + }); +} + +export function getFallbackRecipient(crossSwap: CrossSwap) { + return crossSwap.refundOnOrigin + ? constants.AddressZero + : crossSwap.refundAddress ?? crossSwap.depositor; } -export const swapAndBridgeDexes = Object.keys( - ENABLED_ROUTES.swapAndBridgeAddresses -); +export async function extractDepositDataStruct( + crossSwapQuotes: CrossSwapQuotes, + submissionFees?: { + amount: BigNumberish; + recipient: string; + } +) { + 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 baseDepositData = { + 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 { + inputAmount: baseDepositData.inputAmount, + baseDepositData, + submissionFees: submissionFees || { + amount: "0", + recipient: GAS_SPONSOR_ADDRESS, + }, + }; +} -export function getSwapAndBridgeAddress(dex: string, chainId: number) { - if (!_isDexSupported(dex)) { - throw new UnsupportedDex(dex); +export async function extractSwapAndDepositDataStruct( + crossSwapQuotes: CrossSwapQuotes, + submissionFees?: { + amount: BigNumberish; + recipient: string; } +): Promise { + const { originSwapQuote, contracts } = crossSwapQuotes; + const { originRouter } = contracts; + if (!originSwapQuote || !originRouter) { + throw new Error( + "Can not extract 'SwapAndDepositDataStruct' without originSwapQuote and originRouter" + ); + } + + const { baseDepositData, submissionFees: _submissionFees } = + await extractDepositDataStruct(crossSwapQuotes, submissionFees); + return { + submissionFees: submissionFees || _submissionFees, + 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, + }; +} + +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); +} - const address = ( - ENABLED_ROUTES.swapAndBridgeAddresses[dex] as Record - )?.[chainId]; - if (!address) { - throw new UnsupportedDexOnChain(chainId, dex); +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", + }, + ]; } - return address; + // 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 _isDexSupported( - dex: string -): dex is keyof typeof ENABLED_ROUTES.swapAndBridgeAddresses { - return swapAndBridgeDexes.includes(dex); +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/_eip712.ts b/api/_eip712.ts new file mode 100644 index 000000000..077148f68 --- /dev/null +++ b/api/_eip712.ts @@ -0,0 +1,23 @@ +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, + ] + ) + ); +} diff --git a/api/_erc20.ts b/api/_erc20.ts new file mode 100644 index 000000000..e2cfd5953 --- /dev/null +++ b/api/_erc20.ts @@ -0,0 +1,57 @@ +import { ERC20__factory } from "@across-protocol/contracts"; +import { constants } from "ethers"; + +import { callViaMulticall3, getProvider } from "./_utils"; + +export async function getAllowance(params: { + chainId: number; + tokenAddress: string; + owner: string; + spender: string; +}) { + const erc20 = getErc20(params); + return erc20.allowance(params.owner, params.spender); +} + +export async function getBalance(params: { + chainId: number; + tokenAddress: string; + owner: string; +}) { + const erc20 = getErc20(params); + return erc20.balanceOf(params.owner); +} + +export async function getBalanceAndAllowance(params: { + chainId: number; + tokenAddress: string; + owner: string; + spender: string; +}) { + const provider = getProvider(params.chainId); + + if (params.tokenAddress === constants.AddressZero) { + const balance = await provider.getBalance(params.owner); + return { balance, allowance: constants.MaxUint256 }; + } + + const erc20 = getErc20(params); + const [balance, allowance] = await callViaMulticall3(provider, [ + { + contract: erc20, + functionName: "balanceOf", + args: [params.owner], + }, + { + contract: erc20, + functionName: "allowance", + args: [params.owner, params.spender], + }, + ]); + return { balance: balance[0], allowance: allowance[0] }; +} + +export function getErc20(params: { chainId: number; tokenAddress: string }) { + const provider = getProvider(params.chainId); + return ERC20__factory.connect(params.tokenAddress, provider); +} diff --git a/api/_errors.ts b/api/_errors.ts index b48ac2651..a9e2b978e 100644 --- a/api/_errors.ts +++ b/api/_errors.ts @@ -74,6 +74,15 @@ export class AcrossApiError extends Error { } } +export class TokenNotFoundError extends AcrossApiError { + constructor(args: { address: string; chainId: number; opts?: ErrorOptions }) { + super({ + message: `Unable to find tokenDetails for address: ${args.address}, on chain with id: ${args.chainId}`, + status: HttpErrorToStatusCode.NOT_FOUND, + }); + } +} + export class UnauthorizedError extends AcrossApiError { constructor(args?: { message: string }, opts?: ErrorOptions) { super( @@ -232,7 +241,7 @@ export function handleErrorCondition( { cause: error } ); } else { - const message = `Upstream http request to ${error.request?.url} failed with ${error.status} ${error.message}`; + const message = `Upstream http request to ${error.request?.host} failed with ${error.response?.status}`; acrossApiError = new AcrossApiError( { message, @@ -267,6 +276,7 @@ export function handleErrorCondition( at: endpoint, code: acrossApiError.code, message: `Status ${acrossApiError.status} - ${acrossApiError.message}`, + cause: acrossApiError.cause, }); return response.status(acrossApiError.status).json(acrossApiError); diff --git a/api/_integrator-id.ts b/api/_integrator-id.ts new file mode 100644 index 000000000..982971eeb --- /dev/null +++ b/api/_integrator-id.ts @@ -0,0 +1,29 @@ +import { utils } from "ethers"; + +export const DOMAIN_CALLDATA_DELIMITER = "0x1dc0de"; + +export function isValidIntegratorId(integratorId: string) { + return ( + utils.isHexString(integratorId) && + // "0x" + 2 bytes = 6 hex characters + integratorId.length === 6 + ); +} + +export function assertValidIntegratorId(integratorId: string) { + if (!isValidIntegratorId(integratorId)) { + throw new Error( + `Invalid integrator ID: ${integratorId}. Needs to be 2 bytes hex string.` + ); + } + + return true; +} + +export function tagIntegratorId(integratorId: string, txData: string) { + assertValidIntegratorId(integratorId); + + return utils.hexlify( + utils.concat([txData, DOMAIN_CALLDATA_DELIMITER, integratorId]) + ); +} diff --git a/api/_multicall-handler.ts b/api/_multicall-handler.ts new file mode 100644 index 000000000..a4c8ef6c1 --- /dev/null +++ b/api/_multicall-handler.ts @@ -0,0 +1,79 @@ +import { BigNumber, ethers } from "ethers"; + +export function getMultiCallHandlerAddress(chainId: number) { + // @todo: use API to source addresses? + const defaultAddress = "0x924a9f036260DdD5808007E1AA95f08eD08aA569"; + switch (chainId) { + case 324: + return "0x863859ef502F0Ee9676626ED5B418037252eFeb2"; + case 59144: + return "0x1015c58894961F4F7Dd7D68ba033e28Ed3ee1cDB"; + default: + return defaultAddress; + } +} + +export function buildMulticallHandlerMessage(params: { + actions: Array<{ + target: string; + callData: string; + value: string; + }>; + fallbackRecipient: string; +}) { + const abiCoder = ethers.utils.defaultAbiCoder; + + return abiCoder.encode( + [ + "tuple(" + + "tuple(" + + "address target," + + "bytes callData," + + "uint256 value" + + ")[]," + + "address fallbackRecipient" + + ")", + ], + [ + [ + params.actions.map(({ target, callData, value }) => ({ + target, + callData, + value: BigNumber.from(value), + })), + params.fallbackRecipient, + ], + ] + ); +} + +export function encodeWethWithdrawCalldata(value: ethers.BigNumber) { + const withdrawFunction = "function withdraw(uint256 wad)"; + const wethInterface = new ethers.utils.Interface([withdrawFunction]); + return wethInterface.encodeFunctionData("withdraw", [value]); +} + +export function encodeApproveCalldata( + spender: string, + value: ethers.BigNumber +) { + const approveFunction = "function approve(address spender, uint256 value)"; + const erc20Interface = new ethers.utils.Interface([approveFunction]); + return erc20Interface.encodeFunctionData("approve", [spender, value]); +} + +export function encodeTransferCalldata(to: string, value: ethers.BigNumber) { + const approveFunction = "function transfer(address to, uint256 value)"; + const erc20Interface = new ethers.utils.Interface([approveFunction]); + return erc20Interface.encodeFunctionData("transfer", [to, value]); +} + +export function encodeDrainCalldata(token: string, destination: string) { + const drainFunction = + "function drainLeftoverTokens(address token, address payable destination)"; + const multicallHandlerInterface = new ethers.utils.Interface([drainFunction]); + return multicallHandlerInterface.encodeFunctionData("drainLeftoverTokens", [ + token, + destination, + ]); +} diff --git a/api/_permit.ts b/api/_permit.ts new file mode 100644 index 000000000..f484c7bb7 --- /dev/null +++ b/api/_permit.ts @@ -0,0 +1,150 @@ +import { BigNumberish, ethers } from "ethers"; + +import { getProvider } from "./_utils"; +import { ERC20_PERMIT_ABI } from "./_abis"; +import { hashDomainSeparator } from "./_eip712"; + +export class PermitNotSupportedError extends Error { + constructor(tokenAddress: string, cause?: Error) { + super(`ERC-20 contract ${tokenAddress} does not support permit`, { + cause, + }); + } +} + +export class PermitDomainSeparatorMismatchError extends Error { + constructor(tokenAddress: string) { + super(`Permit EIP712 domain separator mismatch for ${tokenAddress}`); + } +} + +export async function getPermitTypedData(params: { + tokenAddress: string; + chainId: number; + ownerAddress: string; + spenderAddress: string; + value: BigNumberish; + deadline: number; + eip712DomainVersion?: number; +}) { + const { name, domainSeparator, eip712DomainVersion, nonce } = + await getPermitArgsFromContract(params); + + return { + domainSeparator, + eip712: { + types: { + 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), + }, + }, + }; +} + +export async function getPermitArgsFromContract(params: { + tokenAddress: string; + chainId: number; + ownerAddress: string; + spenderAddress: string; + value: BigNumberish; + 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 PermitNotSupportedError(params.tokenAddress, error); + } + + const name: string = nameResult.value; + const versionFromContract = + versionFromContractResult.status === "fulfilled" + ? versionFromContractResult.value + : undefined; + const nonce: BigNumberish = nonceResult.value; + const domainSeparator: string = 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 PermitDomainSeparatorMismatchError(params.tokenAddress); + } + + return { + name, + domainSeparator, + eip712DomainVersion, + nonce, + }; +} diff --git a/api/_spoke-pool-periphery.ts b/api/_spoke-pool-periphery.ts new file mode 100644 index 000000000..14807851f --- /dev/null +++ b/api/_spoke-pool-periphery.ts @@ -0,0 +1,308 @@ +import { SpokePoolPeripheryProxy__factory } from "./_typechain/factories/SpokePoolPeripheryProxy__factory"; +import { SpokePoolV3Periphery__factory } from "./_typechain/factories/SpokePoolV3Periphery__factory"; +import { ENABLED_ROUTES, getProvider } from "./_utils"; +import { SpokePoolV3PeripheryInterface } from "./_typechain/SpokePoolV3Periphery"; + +const sharedEIP712Types = { + 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 class UnknownPeripheryProxyOnChain extends Error { + constructor(chainId: number) { + super(`Unknown 'SpokePoolPeripheryProxy' on chain ${chainId}`); + } +} + +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; +} + +export function getSpokePoolPeripheryProxyAddress(chainId: number) { + const address = + ENABLED_ROUTES.spokePoolPeripheryProxyAddresses[ + chainId as keyof typeof ENABLED_ROUTES.spokePoolPeripheryProxyAddresses + ]; + if (!address) { + throw new UnknownPeripheryProxyOnChain(chainId); + } + return address; +} + +export function getSpokePoolPeriphery(address: string, chainId: number) { + return SpokePoolV3Periphery__factory.connect(address, getProvider(chainId)); +} + +export function getSpokePoolPeripheryProxy(address: string, chainId: number) { + return SpokePoolPeripheryProxy__factory.connect( + address, + getProvider(chainId) + ); +} + +export async function getDepositTypedData(params: { + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct; + 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: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct; + 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, + }, + }; +} + +export function encodeDepositWithPermitCalldata(args: { + signatureOwner: string; + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct; + deadline: number; + permitSignature: string; + depositDataSignature: string; +}) { + return SpokePoolV3Periphery__factory.createInterface().encodeFunctionData( + "depositWithPermit", + [ + args.signatureOwner, + args.depositData, + args.deadline, + args.permitSignature, + args.depositDataSignature, + ] + ); +} + +export function encodeSwapAndBridgeWithPermitCalldata(args: { + signatureOwner: string; + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct; + deadline: number; + permitSignature: string; + swapAndDepositDataSignature: string; +}) { + return SpokePoolV3Periphery__factory.createInterface().encodeFunctionData( + "swapAndBridgeWithPermit", + [ + args.signatureOwner, + args.swapAndDepositData, + args.deadline, + args.permitSignature, + args.swapAndDepositDataSignature, + ] + ); +} + +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; + swapAndDepositDataSignature: string; +}) { + return SpokePoolV3Periphery__factory.createInterface().encodeFunctionData( + "swapAndBridgeWithAuthorization", + [ + args.signatureOwner, + args.swapAndDepositData, + args.validAfter, + args.validBefore, + args.nonce, + args.receiveWithAuthSignature, + args.swapAndDepositDataSignature, + ] + ); +} 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/_transfer-with-auth.ts b/api/_transfer-with-auth.ts new file mode 100644 index 000000000..5e88a7b6e --- /dev/null +++ b/api/_transfer-with-auth.ts @@ -0,0 +1,134 @@ +import { BigNumberish, ethers } from "ethers"; +import { getProvider } from "./_utils"; +import { ERC_TRANSFER_WITH_AUTH_ABI } from "./_abis"; +import { hashDomainSeparator } from "./_eip712"; + +export class TransferWithAuthNotSupportedError extends Error { + constructor(tokenAddress: string, cause?: Error) { + super( + `ERC-20 contract ${tokenAddress} does not support transfer with authorization`, + { + cause, + } + ); + } +} + +export class TransferWithAuthDomainSeparatorMismatchError extends Error { + constructor(tokenAddress: string) { + super( + `Transfer with authorization EIP712 domain separator mismatch for ${tokenAddress}` + ); + } +} + +export async function getReceiveWithAuthTypedData(params: { + tokenAddress: string; + chainId: number; + ownerAddress: string; + spenderAddress: string; + value: BigNumberish; + validBefore: number; + nonce: string; + validAfter?: number; + eip712DomainVersion?: number; +}) { + const { name, domainSeparator, eip712DomainVersion } = + await getReceiveWithAuthArgsFromContract(params); + + return { + domainSeparator, + eip712: { + types: { + ReceiveWithAuthorization: [ + { 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: "ReceiveWithAuthorization", + message: { + from: params.ownerAddress, + to: params.spenderAddress, + value: String(params.value), + validAfter: params.validAfter ?? 0, + validBefore: params.validBefore, + nonce: params.nonce, // non-sequential nonce, random 32 byte hex string + }, + }, + }; +} + +export async function getReceiveWithAuthArgsFromContract(params: { + tokenAddress: string; + chainId: number; + ownerAddress: string; + spenderAddress: string; + value: BigNumberish; + 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 TransferWithAuthNotSupportedError(params.tokenAddress, error); + } + + const name: string = nameResult.value; + const versionFromContract = + versionFromContractResult.status === "fulfilled" + ? versionFromContractResult.value + : undefined; + const domainSeparator: string = 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 TransferWithAuthDomainSeparatorMismatchError(params.tokenAddress); + } + + return { + name, + domainSeparator, + eip712DomainVersion, + }; +} 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 new file mode 100644 index 000000000..af3321c12 --- /dev/null +++ b/api/_typechain/SpokePoolV3Periphery.ts @@ -0,0 +1,1007 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumber, + BigNumberish, + BytesLike, + CallOverrides, + ContractTransaction, + Overrides, + PayableOverrides, + PopulatedTransaction, + Signer, + utils, +} from "ethers"; +import type { + FunctionFragment, + Result, + EventFragment, +} from "@ethersproject/abi"; +import type { Listener, Provider } from "@ethersproject/providers"; +import type { + TypedEventFilter, + TypedEvent, + TypedListener, + OnEvent, +} from "@across-protocol/contracts/dist/typechain/common"; + +export declare namespace 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 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: { + "deposit(address,address,uint256,uint256,uint256,address,uint32,uint32,uint32,bytes)": 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(((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: + | "deposit" + | "depositWithAuthorization" + | "depositWithPermit" + | "depositWithPermit2" + | "domainSeparator" + | "eip712Domain" + | "initialize" + | "isValidSignature" + | "multicall" + | "permit2" + | "proxy" + | "spokePool" + | "swapAndBridge" + | "swapAndBridgeWithAuthorization" + | "swapAndBridgeWithPermit" + | "swapAndBridgeWithPermit2" + | "wrappedNativeToken" + ): FunctionFragment; + + encodeFunctionData( + functionFragment: "deposit", + values: [ + string, + string, + BigNumberish, + BigNumberish, + BigNumberish, + string, + BigNumberish, + BigNumberish, + BigNumberish, + BytesLike, + ] + ): string; + encodeFunctionData( + functionFragment: "depositWithAuthorization", + values: [ + string, + SpokePoolV3PeripheryInterface.DepositDataStruct, + BigNumberish, + BigNumberish, + BytesLike, + BytesLike, + BytesLike, + ] + ): string; + encodeFunctionData( + functionFragment: "depositWithPermit", + values: [ + string, + SpokePoolV3PeripheryInterface.DepositDataStruct, + BigNumberish, + BytesLike, + BytesLike, + ] + ): 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, 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: [SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct] + ): string; + encodeFunctionData( + functionFragment: "swapAndBridgeWithAuthorization", + values: [ + string, + SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + BigNumberish, + BigNumberish, + BytesLike, + BytesLike, + BytesLike, + ] + ): string; + encodeFunctionData( + functionFragment: "swapAndBridgeWithPermit", + values: [ + string, + 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: "deposit", data: BytesLike): Result; + decodeFunctionResult( + functionFragment: "depositWithAuthorization", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "depositWithPermit", + 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", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "swapAndBridgeWithAuthorization", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "swapAndBridgeWithPermit", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "swapAndBridgeWithPermit2", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "wrappedNativeToken", + data: BytesLike + ): Result; + + events: { + "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; + acrossInputAmount: BigNumber; + acrossOutputToken: string; + acrossOutputAmount: BigNumber; +} +export type SwapBeforeBridgeEvent = TypedEvent< + [string, string, string, string, BigNumber, BigNumber, string, BigNumber], + SwapBeforeBridgeEventObject +>; + +export type SwapBeforeBridgeEventFilter = + TypedEventFilter; + +export interface SpokePoolV3Periphery extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + interface: SpokePoolV3PeripheryInterface; + + 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: { + deposit( + recipient: string, + inputToken: string, + inputAmount: BigNumberish, + outputAmount: BigNumberish, + destinationChainId: BigNumberish, + exclusiveRelayer: string, + quoteTimestamp: BigNumberish, + fillDeadline: BigNumberish, + exclusivityParameter: BigNumberish, + message: BytesLike, + overrides?: PayableOverrides & { from?: string } + ): Promise; + + depositWithAuthorization( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + receiveWithAuthSignature: BytesLike, + depositDataSignature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + depositWithPermit( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + deadline: BigNumberish, + 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; + + 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, + _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( + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + overrides?: PayableOverrides & { from?: string } + ): Promise; + + swapAndBridgeWithAuthorization( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + receiveWithAuthSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridgeWithPermit( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + deadline: BigNumberish, + 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<[string]>; + }; + + deposit( + recipient: string, + inputToken: string, + inputAmount: BigNumberish, + outputAmount: BigNumberish, + destinationChainId: BigNumberish, + exclusiveRelayer: string, + quoteTimestamp: BigNumberish, + fillDeadline: BigNumberish, + exclusivityParameter: BigNumberish, + message: BytesLike, + overrides?: PayableOverrides & { from?: string } + ): Promise; + + depositWithAuthorization( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + receiveWithAuthSignature: BytesLike, + depositDataSignature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + depositWithPermit( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + deadline: BigNumberish, + 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; + + 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, + _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( + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + overrides?: PayableOverrides & { from?: string } + ): Promise; + + swapAndBridgeWithAuthorization( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + receiveWithAuthSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridgeWithPermit( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + deadline: BigNumberish, + 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; + + callStatic: { + deposit( + recipient: string, + inputToken: string, + inputAmount: BigNumberish, + outputAmount: BigNumberish, + destinationChainId: BigNumberish, + exclusiveRelayer: string, + quoteTimestamp: BigNumberish, + fillDeadline: BigNumberish, + exclusivityParameter: BigNumberish, + message: BytesLike, + overrides?: CallOverrides + ): Promise; + + depositWithAuthorization( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + receiveWithAuthSignature: BytesLike, + depositDataSignature: BytesLike, + overrides?: CallOverrides + ): Promise; + + depositWithPermit( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + deadline: BigNumberish, + permitSignature: BytesLike, + depositDataSignature: BytesLike, + overrides?: CallOverrides + ): Promise; + + depositWithPermit2( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + permit: IPermit2.PermitTransferFromStruct, + signature: BytesLike, + 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, + _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( + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + overrides?: CallOverrides + ): Promise; + + swapAndBridgeWithAuthorization( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + receiveWithAuthSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, + overrides?: CallOverrides + ): Promise; + + swapAndBridgeWithPermit( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + deadline: BigNumberish, + 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: { + "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, + acrossInputAmount?: null, + acrossOutputToken?: string | null, + acrossOutputAmount?: null + ): SwapBeforeBridgeEventFilter; + SwapBeforeBridge( + exchange?: null, + exchangeCalldata?: null, + swapToken?: string | null, + acrossInputToken?: string | null, + swapTokenAmount?: null, + acrossInputAmount?: null, + acrossOutputToken?: string | null, + acrossOutputAmount?: null + ): SwapBeforeBridgeEventFilter; + }; + + estimateGas: { + deposit( + recipient: string, + inputToken: string, + inputAmount: BigNumberish, + outputAmount: BigNumberish, + destinationChainId: BigNumberish, + exclusiveRelayer: string, + quoteTimestamp: BigNumberish, + fillDeadline: BigNumberish, + exclusivityParameter: BigNumberish, + message: BytesLike, + overrides?: PayableOverrides & { from?: string } + ): Promise; + + depositWithAuthorization( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + receiveWithAuthSignature: BytesLike, + depositDataSignature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + depositWithPermit( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + deadline: BigNumberish, + 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; + + domainSeparator(overrides?: CallOverrides): Promise; + + eip712Domain(overrides?: CallOverrides): Promise; + + initialize( + _spokePool: string, + _wrappedNativeToken: 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( + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + overrides?: PayableOverrides & { from?: string } + ): Promise; + + swapAndBridgeWithAuthorization( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + receiveWithAuthSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridgeWithPermit( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + deadline: BigNumberish, + 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: { + deposit( + recipient: string, + inputToken: string, + inputAmount: BigNumberish, + outputAmount: BigNumberish, + destinationChainId: BigNumberish, + exclusiveRelayer: string, + quoteTimestamp: BigNumberish, + fillDeadline: BigNumberish, + exclusivityParameter: BigNumberish, + message: BytesLike, + overrides?: PayableOverrides & { from?: string } + ): Promise; + + depositWithAuthorization( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + receiveWithAuthSignature: BytesLike, + depositDataSignature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + depositWithPermit( + signatureOwner: string, + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct, + deadline: BigNumberish, + 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; + + domainSeparator(overrides?: CallOverrides): Promise; + + eip712Domain(overrides?: CallOverrides): Promise; + + initialize( + _spokePool: string, + _wrappedNativeToken: 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( + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + overrides?: PayableOverrides & { from?: string } + ): Promise; + + swapAndBridgeWithAuthorization( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + validAfter: BigNumberish, + validBefore: BigNumberish, + nonce: BytesLike, + receiveWithAuthSignature: BytesLike, + swapAndDepositDataSignature: BytesLike, + overrides?: Overrides & { from?: string } + ): Promise; + + swapAndBridgeWithPermit( + signatureOwner: string, + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct, + deadline: BigNumberish, + 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__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 6e38465f1..48992fb4f 100644 --- a/api/_utils.ts +++ b/api/_utils.ts @@ -5,6 +5,9 @@ import { SpokePool, SpokePool__factory, } from "@across-protocol/contracts/dist/typechain"; +// NOTE: We are still on v3.0.6 of verifier deployments until audit went through. Because the interface changed, we need to use the old factory. +// export { SpokePoolVerifier__factory } from "@across-protocol/contracts/dist/typechain/factories/contracts/SpokePoolVerifier__factory"; +import { SpokePoolVerifier__factory } from "@across-protocol/contracts-v3.0.6/dist/typechain/factories/contracts/SpokePoolVerifier__factory"; import acrossDeployments from "@across-protocol/contracts/dist/deployments/deployments.json"; import * as sdk from "@across-protocol/sdk"; import { asL2Provider } from "@eth-optimism/sdk"; @@ -14,7 +17,7 @@ import { BalancerNetworkConfig, Multicall3, } from "@balancer-labs/sdk"; -import axios from "axios"; +import axios, { AxiosError } from "axios"; import { BigNumber, BigNumberish, @@ -31,6 +34,7 @@ import { Infer, integer, min, + size, string, Struct, } from "superstruct"; @@ -65,7 +69,7 @@ import { maxRelayFeePct, relayerFeeCapitalCostConfig, } from "./_constants"; -import { PoolStateOfUser, PoolStateResult } from "./_types"; +import { PoolStateOfUser, PoolStateResult, TokenInfo } from "./_types"; import { buildInternalCacheKey, getCachedValue, @@ -75,9 +79,15 @@ import { MissingParamError, InvalidParamError, RouteNotEnabledError, + AcrossApiError, + HttpErrorToStatusCode, + AcrossErrorCode, + TokenNotFoundError, } from "./_errors"; +import { Token } from "./_dexes/types"; export { InputError, handleErrorCondition } from "./_errors"; +export const { Profiler } = sdk.utils; type LoggingUtility = sdk.relayFeeCalculator.Logger; type RpcProviderName = keyof typeof rpcProvidersJson.providers.urls; @@ -192,7 +202,10 @@ export const getLogger = (): LoggingUtility => { * Resolves the current vercel endpoint dynamically * @returns A valid URL of the current endpoint in vercel */ -export const resolveVercelEndpoint = () => { +export const resolveVercelEndpoint = (omitOverride = false) => { + if (!omitOverride && process.env.REACT_APP_VERCEL_API_BASE_URL_OVERRIDE) { + return process.env.REACT_APP_VERCEL_API_BASE_URL_OVERRIDE; + } const url = process.env.VERCEL_URL ?? "across.to"; const env = process.env.VERCEL_ENV ?? "development"; switch (env) { @@ -352,6 +365,14 @@ function getStaticIsContract(chainId: number, address: string) { return !!deployedAcrossContract; } +export function getWrappedNativeTokenAddress(chainId: number) { + if (chainId === CHAIN_IDs.POLYGON || chainId === CHAIN_IDs.POLYGON_AMOY) { + return TOKEN_SYMBOLS_MAP.WMATIC.addresses[chainId]; + } + + return TOKEN_SYMBOLS_MAP.WETH.addresses[chainId]; +} + /** * Utility function to resolve route details based on given `inputTokenAddress` and `destinationChainId`. * The optional parameter `originChainId` can be omitted if the `inputTokenAddress` is unique across all @@ -759,12 +780,18 @@ export const buildDepositForSimulation = (depositArgs: { export const getCachedTokenPrice = async ( l1Token: string, baseCurrency: string = "eth", - historicalDateISO?: string + historicalDateISO?: string, + chainId?: number ): Promise => { return Number( ( await axios(`${resolveVercelEndpoint()}/api/coingecko`, { - params: { l1Token, baseCurrency, date: historicalDateISO }, + params: { + l1Token, + chainId, + baseCurrency, + date: historicalDateISO, + }, }) ).data.price ); @@ -817,6 +844,173 @@ export const getCachedLimits = async ( ).data; }; +export async function getSuggestedFees(params: { + inputToken: string; + outputToken: string; + originChainId: number; + destinationChainId: number; + amount: string; + skipAmountLimit?: boolean; + message?: string; + depositMethod?: string; + recipient?: string; +}): Promise<{ + estimatedFillTimeSec: number; + timestamp: number; + isAmountTooLow: boolean; + quoteBlock: string; + exclusiveRelayer: string; + exclusivityDeadline: number; + spokePoolAddress: string; + destinationSpokePoolAddress: string; + totalRelayFee: { + pct: string; + total: string; + }; + relayerCapitalFee: { + pct: string; + total: string; + }; + relayerGasFee: { + pct: string; + total: string; + }; + lpFee: { + pct: string; + total: string; + }; + limits: { + minDeposit: string; + maxDeposit: string; + maxDepositInstant: string; + maxDepositShortDelay: string; + recommendedDepositInstant: string; + }; +}> { + return ( + await axios(`${resolveVercelEndpoint()}/api/suggested-fees`, { + params, + }) + ).data; +} + +export async function getBridgeQuoteForMinOutput(params: { + inputToken: Token; + outputToken: Token; + minOutputAmount: BigNumber; + recipient?: string; + message?: string; +}) { + const maxTries = 3; + const tryChunkSize = 3; + const baseParams = { + inputToken: params.inputToken.address, + outputToken: params.outputToken.address, + originChainId: params.inputToken.chainId, + destinationChainId: params.outputToken.chainId, + skipAmountLimit: true, + recipient: params.recipient, + message: params.message, + }; + + try { + // 1. Use the suggested fees to get an indicative quote with + // input amount equal to minOutputAmount + let tries = 0; + let adjustedInputAmount = addMarkupToAmount(params.minOutputAmount, 0.005); + let indicativeQuote = await getSuggestedFees({ + ...baseParams, + amount: adjustedInputAmount.toString(), + }); + let adjustmentPct = indicativeQuote.totalRelayFee.pct; + let finalQuote: Awaited> | undefined = + undefined; + + // 2. Adjust input amount to meet minOutputAmount + while (tries < maxTries) { + const inputAmounts = Array.from({ length: tryChunkSize }).map((_, i) => { + const buffer = 0.001 * i; + return addMarkupToAmount( + adjustedInputAmount + .mul(utils.parseEther("1").add(adjustmentPct)) + .div(sdk.utils.fixedPointAdjustment), + buffer + ); + }); + const quotes = await Promise.all( + inputAmounts.map((inputAmount) => { + return getSuggestedFees({ + ...baseParams, + amount: inputAmount.toString(), + }); + }) + ); + + for (const [i, quote] of Object.entries(quotes)) { + const inputAmount = inputAmounts[Number(i)]; + const outputAmount = inputAmount.sub( + inputAmount + .mul(quote.totalRelayFee.pct) + .div(sdk.utils.fixedPointAdjustment) + ); + if (outputAmount.gte(params.minOutputAmount)) { + finalQuote = quote; + adjustedInputAmount = inputAmount; + break; + } + } + + if (finalQuote) { + break; + } + + adjustedInputAmount = inputAmounts[inputAmounts.length - 1]; + tries++; + } + + if (!finalQuote) { + throw new Error("Failed to adjust input amount to meet minOutputAmount"); + } + + return { + inputAmount: adjustedInputAmount, + outputAmount: adjustedInputAmount.sub(finalQuote.totalRelayFee.total), + minOutputAmount: params.minOutputAmount, + suggestedFees: finalQuote, + message: params.message, + inputToken: params.inputToken, + outputToken: params.outputToken, + }; + } catch (err) { + if (err instanceof AxiosError) { + const { response = { data: {} } } = err; + // If upstream error is an AcrossApiError, we just return it + if (response?.data?.type === "AcrossApiError") { + throw new AcrossApiError( + { + message: response.data.message, + status: response.data.status, + code: response.data.code, + param: response.data.param, + }, + { cause: err } + ); + } else { + const message = `Upstream http request to ${err.request?.host} failed with ${err.response?.status}`; + throw new AcrossApiError( + { + message, + status: HttpErrorToStatusCode.BAD_GATEWAY, + code: AcrossErrorCode.UPSTREAM_HTTP_ERROR, + }, + { cause: err } + ); + } + } + throw err; + } +} + export const providerCache: Record = {}; /** @@ -953,6 +1147,60 @@ export const isRouteEnabled = ( return !!enabled; }; +export function isInputTokenBridgeable( + inputTokenAddress: string, + originChainId: number +) { + return ENABLED_ROUTES.routes.some( + ({ fromTokenAddress, fromChain }) => + originChainId === fromChain && + inputTokenAddress.toLowerCase() === fromTokenAddress.toLowerCase() + ); +} + +export function isOutputTokenBridgeable( + outputTokenAddress: string, + destinationChainId: number +) { + return ENABLED_ROUTES.routes.some( + ({ toTokenAddress, toChain }) => + destinationChainId === toChain && + toTokenAddress.toLowerCase() === outputTokenAddress.toLowerCase() + ); +} + +export function getRouteByInputTokenAndDestinationChain( + inputTokenAddress: string, + destinationChainId: number +) { + return ENABLED_ROUTES.routes.find( + ({ fromTokenAddress, toChain }) => + destinationChainId === toChain && + fromTokenAddress.toLowerCase() === inputTokenAddress.toLowerCase() + ); +} + +export function getRouteByOutputTokenAndOriginChain( + outputTokenAddress: string, + originChainId: number +) { + return ENABLED_ROUTES.routes.find( + ({ toTokenAddress, fromChain }) => + originChainId === fromChain && + outputTokenAddress.toLowerCase() === toTokenAddress.toLowerCase() + ); +} + +export function getRoutesByChainIds( + originChainId: number, + destinationChainId: number +) { + return ENABLED_ROUTES.routes.filter( + ({ toChain, fromChain }) => + originChainId === fromChain && destinationChainId === toChain + ); +} + /** * Resolves the balance of a given ERC20 token at a provided address. If no token is provided, the balance of the * native currency will be returned. @@ -1240,7 +1488,7 @@ export function validAddressOrENS() { export function positiveIntStr() { return define("positiveIntStr", (value) => { - return Number.isInteger(Number(value)) && Number(value) > 0; + return Number.isInteger(Number(value)) && Number(value) >= 0; }); } @@ -1256,6 +1504,16 @@ export function boolStr() { }); } +export function hexString() { + return define("hexString", (value) => { + return utils.isHexString(value); + }); +} + +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 @@ -1997,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 { @@ -2020,3 +2280,126 @@ export function paramToArray( if (!param) return; return Array.isArray(param) ? param : [param]; } + +export type TokenOptions = { + chainId: number; + address: string; +}; + +const TTL_TOKEN_INFO = 30 * 24 * 60 * 60; // 30 days + +function tokenInfoCache(params: TokenOptions) { + return makeCacheGetterAndSetter( + buildInternalCacheKey("tokenInfo", params.address, params.chainId), + TTL_TOKEN_INFO, + () => getTokenInfo(params), + (tokenDetails) => tokenDetails + ); +} + +export async function getCachedTokenInfo(params: TokenOptions) { + return tokenInfoCache(params).get(); +} + +// find decimals and symbol for any token address on any chain we support +export async function getTokenInfo({ chainId, address }: TokenOptions): Promise< + Pick & { + chainId: number; + } +> { + try { + if (!ethers.utils.isAddress(address)) { + throw new InvalidParamError({ + param: "address", + message: '"Address" must be a valid ethereum address', + }); + } + + if (!Number.isSafeInteger(chainId) || chainId < 0) { + throw new InvalidParamError({ + param: "chainId", + message: '"chainId" must be a positive integer', + }); + } + + // ERC20 resolved statically + const token = Object.values(TOKEN_SYMBOLS_MAP).find((token) => + Boolean( + token.addresses?.[chainId]?.toLowerCase() === address.toLowerCase() + ) + ); + + if (token) { + return { + decimals: token.decimals, + symbol: token.symbol, + address: token.addresses[chainId], + name: token.name, + chainId, + }; + } + + // ERC20 resolved dynamically + const provider = getProvider(chainId); + + const erc20 = ERC20__factory.connect( + ethers.utils.getAddress(address), + provider + ); + + const calls = [ + { + contract: erc20, + functionName: "decimals", + }, + { + contract: erc20, + functionName: "symbol", + }, + { + contract: erc20, + functionName: "name", + }, + ]; + + const [[decimals], [symbol], [name]] = await callViaMulticall3( + provider, + calls + ); + + return { + address, + decimals, + symbol, + name, + chainId, + }; + } catch (error) { + throw new TokenNotFoundError({ + chainId, + address, + opts: { + cause: error, + }, + }); + } +} + +export function getSpokePoolVerifier(chainId: number) { + const isSpokePoolVerifierDeployed = ( + ENABLED_ROUTES.spokePoolVerifier.enabledChains as number[] + ).includes(chainId); + + if (!isSpokePoolVerifierDeployed) { + return undefined; + } + + 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/relay/_queue.ts b/api/relay/_queue.ts new file mode 100644 index 000000000..aac70b346 --- /dev/null +++ b/api/relay/_queue.ts @@ -0,0 +1,46 @@ +import { Client } from "@upstash/qstash"; + +import { RelayRequest, RelayStrategy, RelayStrategyName } from "./_types"; +import { resolveVercelEndpoint } from "../_utils"; + +const client = new Client({ + token: process.env.QSTASH_TOKEN!, +}); + +export async function pushRelayRequestToQueue({ + request, + strategy, +}: { + request: RelayRequest; + strategy: RelayStrategy; +}) { + const strategyName = strategy.strategyName; + const queue = getRelayRequestQueue(strategyName, request.chainId); + await queue.upsert({ + parallelism: strategy.queueParallelism, + }); + + const baseUrl = resolveVercelEndpoint(true); + const response = await queue.enqueueJSON({ + retries: 3, + contentBasedDeduplication: true, + headers: new Headers({ + "Retry-After": "1", + }), + url: `${baseUrl}/api/relay/jobs/process`, + body: { + request, + strategyName, + }, + }); + return response; +} + +function getRelayRequestQueue( + strategyName: RelayStrategyName, + chainId: number +) { + return client.queue({ + queueName: `relay-request-queue-${chainId}-${strategyName}`, + }); +} diff --git a/api/relay/_strategies/gelato.ts b/api/relay/_strategies/gelato.ts new file mode 100644 index 000000000..9fbf20ca4 --- /dev/null +++ b/api/relay/_strategies/gelato.ts @@ -0,0 +1,120 @@ +import axios from "axios"; +import { RelayRequest, RelayStrategy } from "../_types"; +import { encodeCalldataForRelayRequest } from "../_utils"; + +const GELATO_API_KEY = process.env.GELATO_API_KEY; + +export function getGelatoStrategy(): RelayStrategy { + return { + strategyName: "gelato", + queueParallelism: 2, + relay: async (request: RelayRequest) => { + const encodedCalldata = encodeCalldataForRelayRequest(request); + + const taskId = await relayWithGelatoApi({ + chainId: request.chainId, + target: request.to, + data: encodedCalldata, + }); + + let txHash: string | undefined; + + while (true) { + const taskStatus = await getGelatoTaskStatus(taskId); + + if ( + ["Cancelled", "NotFound", "ExecReverted", "Blacklisted"].includes( + taskStatus.taskState + ) + ) { + throw new GelatoTaskStatusError(taskStatus); + } + + if (taskStatus.transactionHash) { + txHash = taskStatus.transactionHash; + break; + } + + await new Promise((resolve) => setTimeout(resolve, 1_000)); + } + + return txHash; + }, + }; +} + +const gelatoBaseUrl = "https://api.gelato.digital"; + +async function relayWithGelatoApi({ + chainId, + target, + data, +}: { + chainId: number; + target: string; + data: string; +}) { + if (!GELATO_API_KEY) { + throw new Error("Can not call Gelato API: key is not set"); + } + + const response = await axios.post( + `${gelatoBaseUrl}/relays/v2/sponsored-call`, + { + chainId, + target, + data, + sponsorApiKey: GELATO_API_KEY, + } + ); + + return response.data.taskId as string; +} + +type TaskStatus = { + taskState: + | "CheckPending" + | "ExecPending" + | "ExecSuccess" + | "ExecReverted" + | "WaitingForConfirmation" + | "Blacklisted" + | "Cancelled" + | "NotFound"; + chainId: number; + taskId: string; + creationDate: string; + lastCheckDate?: string; + lastCheckMessage?: string; + transactionHash?: string; + blockNumber?: number; + executionDate?: string; + gasUsed?: string; + effectiveGasPrice?: string; +}; + +async function getGelatoTaskStatus(taskId: string) { + const response = await axios.get<{ task: TaskStatus }>( + `${gelatoBaseUrl}/tasks/status/${taskId}` + ); + return response.data.task; +} + +class GelatoTaskStatusError extends Error { + taskStatus: TaskStatus; + + constructor(taskStatus: TaskStatus) { + super( + `Can not relay request via Gelato due to task state ${taskStatus.taskState}` + ); + this.taskStatus = taskStatus; + } + + toJSON() { + return { + name: this.name, + message: this.message, + taskStatus: this.taskStatus, + }; + } +} diff --git a/api/relay/_strategies/index.ts b/api/relay/_strategies/index.ts new file mode 100644 index 000000000..9b621c0eb --- /dev/null +++ b/api/relay/_strategies/index.ts @@ -0,0 +1,11 @@ +import { RelayStrategy, RelayStrategyName } from "../_types"; +import { getGelatoStrategy } from "./gelato"; +import { getLocalSignersStrategy } from "./local-signers"; + +const gelatoStrategy = getGelatoStrategy(); +const localSignersStrategy = getLocalSignersStrategy(); + +export const strategiesByName = { + [gelatoStrategy.strategyName]: gelatoStrategy, + [localSignersStrategy.strategyName]: localSignersStrategy, +} as Record; diff --git a/api/relay/_strategies/local-signers.ts b/api/relay/_strategies/local-signers.ts new file mode 100644 index 000000000..d895f1b82 --- /dev/null +++ b/api/relay/_strategies/local-signers.ts @@ -0,0 +1,92 @@ +import { Wallet, utils } from "ethers"; + +import { RelayRequest, RelayStrategy } from "../_types"; +import { encodeCalldataForRelayRequest } from "../_utils"; +import { redisCache } from "../../_cache"; +import { getProvider } from "../../_utils"; + +const localSignerPrivateKeys = + process.env.LOCAL_SIGNER_PRIVATE_KEYS!.split(","); +const balanceAlertThreshold = utils.parseEther("0.000001"); // TODO: Refine value + +export function getLocalSignersStrategy(): RelayStrategy { + return { + strategyName: "local-signers", + queueParallelism: 1, // TODO: Should be dynamic based on the number of local signers + relay: async (request: RelayRequest) => { + const encodedCalldata = encodeCalldataForRelayRequest(request); + + if (localSignerPrivateKeys.length === 0) { + throw new Error( + "Can not relay tx via local signers: No local signers found" + ); + } + + for (const signerPrivateKey of localSignerPrivateKeys) { + const provider = getProvider(request.chainId); + const wallet = new Wallet(signerPrivateKey, provider); + try { + await lockSigner(wallet.address, request.chainId); + + const balance = await wallet.getBalance(); + if (balance.lt(balanceAlertThreshold)) { + // TODO: Send PD alert + } + + const txRequest = { + chainId: request.chainId, + to: request.to, + data: encodedCalldata, + from: wallet.address, + }; + const tx = await wallet.sendTransaction(txRequest); + const receipt = await tx.wait(); + return receipt.transactionHash; + } catch (error) { + if (error instanceof SignerLockedError) { + continue; + } + throw error; + } finally { + await unlockSigner(wallet.address, request.chainId); + } + } + + throw new Error( + "Can not relay tx via local signers: All local signers are locked" + ); + }, + }; +} + +async function lockSigner(signerAddress: string, chainId: number) { + const lockKey = getLockKey(signerAddress, chainId); + const lockValue = await redisCache.get(lockKey); + + if (lockValue) { + throw new SignerLockedError(signerAddress, chainId); + } + + await redisCache.set(lockKey, "true", 30); +} + +async function unlockSigner(signerAddress: string, chainId: number) { + const lockKey = getLockKey(signerAddress, chainId); + + const lockValue = await redisCache.get(lockKey); + if (!lockValue) { + return; + } + + await redisCache.del(lockKey); +} + +function getLockKey(signerAddress: string, chainId: number) { + return `signer-lock:${signerAddress}:${chainId}`; +} + +class SignerLockedError extends Error { + constructor(signerAddress: string, chainId: number) { + super(`Signer ${signerAddress} on chain ${chainId} is already locked`); + } +} diff --git a/api/relay/_types.ts b/api/relay/_types.ts new file mode 100644 index 000000000..95ea1076c --- /dev/null +++ b/api/relay/_types.ts @@ -0,0 +1,19 @@ +import { validateMethodArgs } from "./_utils"; + +export type RelayStrategyName = "gelato" | "local-signers"; + +export type RelayRequest = { + chainId: number; + to: string; + methodNameAndArgs: ReturnType; + signatures: { + permit: string; // use this for all auth signatures + deposit: string; + }; +}; + +export type RelayStrategy = { + strategyName: RelayStrategyName; + queueParallelism: number; + relay: (request: RelayRequest) => Promise; +}; diff --git a/api/relay/_utils.ts b/api/relay/_utils.ts new file mode 100644 index 000000000..de5465602 --- /dev/null +++ b/api/relay/_utils.ts @@ -0,0 +1,468 @@ +import { assert, Infer, type } from "superstruct"; +import { utils } from "ethers"; + +import { bytes32, hexString, positiveIntStr, validAddress } from "../_utils"; +import { getPermitTypedData } from "../_permit"; +import { InvalidParamError } from "../_errors"; +import { + encodeDepositWithAuthCalldata, + encodeDepositWithPermitCalldata, + encodeSwapAndBridgeWithAuthCalldata, + encodeSwapAndBridgeWithPermitCalldata, + getDepositTypedData, + getSwapAndDepositTypedData, +} from "../_spoke-pool-periphery"; +import { RelayRequest } from "./_types"; +import { redisCache } from "../_cache"; +import { getReceiveWithAuthTypedData } from "../_transfer-with-auth"; + +export const GAS_SPONSOR_ADDRESS = + process.env.GAS_SPONSOR_ADDRESS ?? + "0x0000000000000000000000000000000000000000"; + +const SubmissionFeesSchema = type({ + amount: positiveIntStr(), + recipient: validAddress(), +}); + +const BaseDepositDataSchema = type({ + inputToken: validAddress(), + outputToken: validAddress(), + outputAmount: positiveIntStr(), + depositor: validAddress(), + recipient: validAddress(), + destinationChainId: positiveIntStr(), + exclusiveRelayer: validAddress(), + quoteTimestamp: positiveIntStr(), + fillDeadline: positiveIntStr(), + exclusivityParameter: positiveIntStr(), + message: hexString(), +}); + +const SwapAndDepositDataSchema = type({ + submissionFees: SubmissionFeesSchema, + depositData: BaseDepositDataSchema, + swapToken: validAddress(), + exchange: validAddress(), + transferType: positiveIntStr(), + swapTokenAmount: positiveIntStr(), + minExpectedInputTokenAmount: positiveIntStr(), + routerCalldata: hexString(), +}); + +const DepositDataSchema = type({ + submissionFees: SubmissionFeesSchema, + baseDepositData: BaseDepositDataSchema, + inputAmount: positiveIntStr(), +}); + +export const DepositWithPermitArgsSchema = type({ + signatureOwner: validAddress(), + depositData: DepositDataSchema, + deadline: positiveIntStr(), +}); + +export const SwapAndDepositWithPermitArgsSchema = type({ + signatureOwner: validAddress(), + swapAndDepositData: SwapAndDepositDataSchema, + deadline: positiveIntStr(), +}); + +export const DepositWithAuthArgsSchema = type({ + signatureOwner: validAddress(), + depositData: DepositDataSchema, + validAfter: positiveIntStr(), + validBefore: positiveIntStr(), + nonce: bytes32(), +}); + +export const SwapAndDepositWithAuthArgsSchema = type({ + signatureOwner: validAddress(), + swapAndDepositData: SwapAndDepositDataSchema, + validAfter: positiveIntStr(), + validBefore: positiveIntStr(), + nonce: bytes32(), +}); + +export const allowedMethodNames = [ + "depositWithPermit", + "swapAndBridgeWithPermit", + "depositWithAuthorization", + "swapAndBridgeWithAuthorization", +] as const; + +export function validateMethodArgs( + methodName: (typeof allowedMethodNames)[number], + args: any +) { + if (methodName === "depositWithPermit") { + assert(args, DepositWithPermitArgsSchema); + return { + args: args as Infer, + methodName, + } as const; + } else if (methodName === "swapAndBridgeWithPermit") { + assert(args, SwapAndDepositWithPermitArgsSchema); + return { + args: args as Infer, + methodName, + } as const; + } else if (methodName === "depositWithAuthorization") { + assert(args, DepositWithAuthArgsSchema); + return { + args: args as Infer, + methodName, + } as const; + } else if (methodName === "swapAndBridgeWithAuthorization") { + assert(args, SwapAndDepositWithAuthArgsSchema); + return { + args: args as Infer, + methodName, + } as const; + } + throw new Error(`Invalid method name: ${methodName}`); +} + +export async function verifySignatures({ + methodNameAndArgs, + signatures, + chainId, + to, +}: RelayRequest) { + const { methodName, args } = methodNameAndArgs; + + let signatureOwner: string; + let getPermitTypedDataPromise: + | ReturnType + | undefined; + let getDepositTypedDataPromise: ReturnType< + typeof getDepositTypedData | typeof getSwapAndDepositTypedData + >; + let getReceiveWithAuthTypedDataPromise: + | ReturnType + | undefined; + + if (methodName === "depositWithPermit") { + const { signatureOwner: _signatureOwner, deadline, depositData } = args; + signatureOwner = _signatureOwner; + getPermitTypedDataPromise = getPermitTypedData({ + tokenAddress: depositData.baseDepositData.inputToken, + chainId, + ownerAddress: signatureOwner, + spenderAddress: to, // SpokePoolV3Periphery address + value: depositData.inputAmount, + deadline: Number(deadline), + }); + getDepositTypedDataPromise = getDepositTypedData({ + chainId, + depositData, + }); + } else if (methodName === "swapAndBridgeWithPermit") { + const { + signatureOwner: _signatureOwner, + deadline, + swapAndDepositData, + } = args; + signatureOwner = _signatureOwner; + getPermitTypedDataPromise = getPermitTypedData({ + tokenAddress: swapAndDepositData.swapToken, + chainId, + ownerAddress: signatureOwner, + spenderAddress: to, // SpokePoolV3Periphery address + value: swapAndDepositData.swapTokenAmount, + deadline: Number(deadline), + }); + getDepositTypedDataPromise = getSwapAndDepositTypedData({ + chainId, + swapAndDepositData, + }); + } else if (methodName === "depositWithAuthorization") { + const { + signatureOwner: _signatureOwner, + validAfter, + validBefore, + nonce, + depositData, + } = args; + signatureOwner = _signatureOwner; + getReceiveWithAuthTypedDataPromise = getReceiveWithAuthTypedData({ + 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; + getReceiveWithAuthTypedDataPromise = getReceiveWithAuthTypedData({ + 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}` + ); + } + + 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 + ); + + 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", + }); + } + } else if (getReceiveWithAuthTypedDataPromise) { + const [authTypedData, depositTypedData] = await Promise.all([ + getReceiveWithAuthTypedDataPromise, + 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", + }); + } + } +} + +export function encodeCalldataForRelayRequest(request: RelayRequest) { + let encodedCalldata: string; + + if (request.methodNameAndArgs.methodName === "depositWithPermit") { + encodedCalldata = encodeDepositWithPermitCalldata({ + ...request.methodNameAndArgs.args, + deadline: Number(request.methodNameAndArgs.args.deadline), + depositDataSignature: request.signatures.deposit, + permitSignature: request.signatures.permit, + }); + } else if ( + request.methodNameAndArgs.methodName === "swapAndBridgeWithPermit" + ) { + encodedCalldata = encodeSwapAndBridgeWithPermitCalldata({ + ...request.methodNameAndArgs.args, + deadline: Number(request.methodNameAndArgs.args.deadline), + swapAndDepositDataSignature: request.signatures.deposit, + permitSignature: request.signatures.permit, + }); + } else if ( + request.methodNameAndArgs.methodName === "depositWithAuthorization" + ) { + encodedCalldata = encodeDepositWithAuthCalldata({ + ...request.methodNameAndArgs.args, + validAfter: Number(request.methodNameAndArgs.args.validAfter), + validBefore: Number(request.methodNameAndArgs.args.validBefore), + nonce: request.methodNameAndArgs.args.nonce, + receiveWithAuthSignature: request.signatures.permit, + depositDataSignature: request.signatures.deposit, + }); + } else if ( + request.methodNameAndArgs.methodName === "swapAndBridgeWithAuthorization" + ) { + encodedCalldata = encodeSwapAndBridgeWithAuthCalldata({ + ...request.methodNameAndArgs.args, + validAfter: Number(request.methodNameAndArgs.args.validAfter), + validBefore: Number(request.methodNameAndArgs.args.validBefore), + nonce: request.methodNameAndArgs.args.nonce, + receiveWithAuthSignature: request.signatures.permit, + swapAndDepositDataSignature: request.signatures.deposit, + }); + } + // TODO: Add cases for `withPermit2` + else { + throw new Error(`Can not encode calldata for relay request`); + } + return encodedCalldata; +} + +export function getRelayRequestHash(request: RelayRequest) { + return utils.keccak256( + utils.defaultAbiCoder.encode( + ["bytes", "bytes"], + [request.signatures.permit, request.signatures.deposit] + ) + ); +} + +type CachedRelayRequest = + | { + status: "pending"; + request: RelayRequest; + messageId: string; + } + | { + status: "success"; + request: RelayRequest; + txHash: string; + messageId: string; + } + | { + status: "failure"; + request: RelayRequest; + error: Error; + messageId: string; + } + | { + status: "unknown"; + }; +// TODO: Refine value +const cachedRelayRequestTTL = 5 * 60 * 60 * 24; // 5 days + +export async function getCachedRelayRequest( + requestOrHash: RelayRequest | string +): Promise { + const cachedRelayRequest = await redisCache.get( + getRelayRequestCacheKey(requestOrHash) + ); + + if (!cachedRelayRequest) { + return { + status: "unknown", + }; + } + + return cachedRelayRequest; +} + +export async function setCachedRelayRequestPending(params: { + messageId: string; + request: RelayRequest; +}) { + await redisCache.set( + getRelayRequestCacheKey(params.request), + { + status: "pending", + messageId: params.messageId, + request: params.request, + }, + cachedRelayRequestTTL + ); +} + +export async function setCachedRelayRequestFailure(params: { + request: RelayRequest; + error: Error; +}) { + const cachedRelayRequest = await getCachedRelayRequest(params.request); + + if (!cachedRelayRequest || cachedRelayRequest.status === "unknown") { + throw new Error("Request not found in cache"); + } + + if (cachedRelayRequest.status === "success") { + throw new Error( + "Can not set 'failure' status for request that is already successful" + ); + } + + await redisCache.set( + getRelayRequestCacheKey(params.request), + { + status: "failure", + messageId: cachedRelayRequest.messageId, + request: cachedRelayRequest.request, + error: params.error, + }, + cachedRelayRequestTTL + ); +} + +export async function setCachedRelayRequestSuccess(params: { + request: RelayRequest; + txHash: string; +}) { + const cachedRelayRequest = await getCachedRelayRequest(params.request); + + if (!cachedRelayRequest || cachedRelayRequest.status === "unknown") { + throw new Error("Request not found in cache"); + } + + await redisCache.set( + getRelayRequestCacheKey(params.request), + { + status: "success", + messageId: cachedRelayRequest.messageId, + request: cachedRelayRequest.request, + txHash: params.txHash, + }, + cachedRelayRequestTTL + ); +} + +function getRelayRequestCacheKey(requestOrHash: RelayRequest | string) { + const requestHash = + typeof requestOrHash === "string" + ? requestOrHash + : getRelayRequestHash(requestOrHash); + return `relay-request:${requestHash}`; +} diff --git a/api/relay/index.ts b/api/relay/index.ts new file mode 100644 index 000000000..4a1d98105 --- /dev/null +++ b/api/relay/index.ts @@ -0,0 +1,92 @@ +import { VercelRequest, VercelResponse } from "@vercel/node"; +import { object, number, assert, enums } from "superstruct"; + +import { handleErrorCondition } from "../_errors"; +import { getLogger, hexString, validAddress } from "../_utils"; +import { + allowedMethodNames, + getRelayRequestHash, + setCachedRelayRequestPending, + validateMethodArgs, + verifySignatures, +} from "./_utils"; +import { strategiesByName } from "./_strategies"; +import { CHAIN_IDs } from "../_constants"; +import { pushRelayRequestToQueue } from "./_queue"; +import { RelayRequest } from "./_types"; + +export const BaseRelayRequestBodySchema = object({ + chainId: number(), + to: validAddress(), + methodName: enums(allowedMethodNames), + argsWithoutSignatures: object(), + signatures: object({ + permit: hexString(), + deposit: hexString(), + }), +}); + +const strategies = { + default: strategiesByName.gelato, + [CHAIN_IDs.WORLD_CHAIN]: strategiesByName["local-signers"], +}; + +export default async function handler( + request: VercelRequest, + response: VercelResponse +) { + const logger = getLogger(); + logger.debug({ + at: "Relay", + message: "Request body", + body: request.body, + }); + + try { + if (request.method !== "POST") { + return response.status(405).json({ error: "Method not allowed" }); + } + + assert(request.body, BaseRelayRequestBodySchema); + + // Validate method-specific request body + const methodNameAndArgs = validateMethodArgs( + request.body.methodName, + request.body.argsWithoutSignatures + ); + + // Verify signatures + const { signatures, chainId, to } = request.body; + await verifySignatures({ + methodNameAndArgs, + signatures, + chainId, + to, + }); + + // Push request to queue + const strategy = strategies[chainId] ?? strategies.default; + const relayRequest: RelayRequest = { + chainId, + to, + methodNameAndArgs, + signatures, + }; + const queueResponse = await pushRelayRequestToQueue({ + request: relayRequest, + strategy, + }); + + // Store requestId in database + await setCachedRelayRequestPending({ + messageId: queueResponse.messageId, + request: relayRequest, + }); + + response.status(200).json({ + requestHash: getRelayRequestHash(relayRequest), + }); + } catch (error) { + return handleErrorCondition("api/relay", response, logger, error); + } +} diff --git a/api/relay/jobs/process.ts b/api/relay/jobs/process.ts new file mode 100644 index 000000000..0d3973bf8 --- /dev/null +++ b/api/relay/jobs/process.ts @@ -0,0 +1,127 @@ +import { VercelRequest, VercelResponse } from "@vercel/node"; +import { assert, enums, number, object, type } from "superstruct"; +import { Receiver } from "@upstash/qstash"; + +import { handleErrorCondition, InvalidParamError } from "../../_errors"; +import { getLogger, hexString, validAddress } from "../../_utils"; +import { + validateMethodArgs, + verifySignatures, + setCachedRelayRequestSuccess, + setCachedRelayRequestFailure, + getCachedRelayRequest, + allowedMethodNames, +} from "../_utils"; +import { RelayRequest, RelayStrategyName } from "../_types"; +import { strategiesByName } from "../_strategies"; + +const messageReceiver = new Receiver({ + currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY!, + nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY!, +}); + +const RelayProcessJobBodySchema = type({ + strategyName: enums(Object.keys(strategiesByName)), + request: type({ + chainId: number(), + to: validAddress(), + methodNameAndArgs: type({ + methodName: enums(allowedMethodNames), + args: object(), + }), + signatures: object({ + permit: hexString(), + deposit: hexString(), + }), + }), +}); + +export default async function handler(req: VercelRequest, res: VercelResponse) { + const logger = getLogger(); + logger.debug({ + at: "Relay/jobs/process", + message: "Request body", + body: req.body, + }); + + try { + if (req.method !== "POST") { + return res.status(405).json({ error: "Method not allowed" }); + } + + // Verify message comes from QSTASH + const isValid = await messageReceiver.verify({ + signature: (req.headers["upstash-signature"] || + req.headers["Upstash-Signature"]) as string, + body: JSON.stringify(req.body), + }); + + if (!isValid) { + return res.status(401).json({ error: "Unauthorized" }); + } + + assert(req.body, RelayProcessJobBodySchema); + const { request, strategyName } = req.body; + + // Validate method-specific request body + const methodNameAndArgs = validateMethodArgs( + request.methodNameAndArgs.methodName, + request.methodNameAndArgs.args + ); + + // Verify user signatures + const { signatures, chainId, to } = request; + await verifySignatures({ + methodNameAndArgs, + signatures, + chainId, + to, + }); + + const strategy = strategiesByName[strategyName as RelayStrategyName]; + const relayRequest: RelayRequest = { + chainId, + to, + methodNameAndArgs, + signatures, + }; + + // Get cached request + const cachedRequest = await getCachedRelayRequest(relayRequest); + + if ( + !cachedRequest || + cachedRequest.status === "unknown" || + cachedRequest.status === "success" + ) { + throw new InvalidParamError({ + param: "request", + message: "Request not found in cache or succeeded already", + }); + } + + const { messageId } = cachedRequest; + + // Handle request via strategy + try { + const txHash = await strategy.relay(relayRequest); + // Store requestId in database + await setCachedRelayRequestSuccess({ + request: relayRequest, + txHash, + }); + return res.status(200).json({ + messageId, + txHash, + }); + } catch (error) { + await setCachedRelayRequestFailure({ + request: relayRequest, + error: error as Error, + }); + throw error; + } + } catch (error) { + return handleErrorCondition("relay/jobs/process", res, logger, error); + } +} diff --git a/api/relay/status.ts b/api/relay/status.ts new file mode 100644 index 000000000..0a029adf4 --- /dev/null +++ b/api/relay/status.ts @@ -0,0 +1,45 @@ +import { VercelResponse } from "@vercel/node"; +import { assert, type, Infer } from "superstruct"; + +import { handleErrorCondition, InputError } from "../_errors"; +import { getLogger, hexString } from "../_utils"; +import { getCachedRelayRequest } from "./_utils"; +import { TypedVercelRequest } from "../_types"; + +const RelayRequestStatusSchema = type({ + requestHash: hexString(), +}); + +type RelayRequestStatusType = Infer; + +export default async function handler( + request: TypedVercelRequest, + response: VercelResponse +) { + const logger = getLogger(); + logger.debug({ + at: "Relay/status", + message: "Request params", + params: request.query, + }); + + try { + assert(request.query, RelayRequestStatusSchema); + + const cachedRelayRequest = await getCachedRelayRequest( + request.query.requestHash + ); + + if (cachedRelayRequest.status === "unknown") { + throw new InputError({ + message: `Request with hash ${request.query.requestHash} is unknown`, + code: "INVALID_PARAM", + param: "requestHash", + }); + } + + response.status(200).json(cachedRelayRequest); + } catch (error) { + return handleErrorCondition("api/relay/status", response, logger, error); + } +} diff --git a/api/swap-quote.ts b/api/swap-quote.ts index d8d626c0d..6bdd6386f 100644 --- a/api/swap-quote.ts +++ b/api/swap-quote.ts @@ -13,9 +13,10 @@ import { validateChainAndTokenParams, isSwapRouteEnabled, } from "./_utils"; -import { getUniswapQuoteAndCalldata } from "./_dexes/uniswap"; -import { get1inchQuoteAndCalldata } from "./_dexes/1inch"; +import { getUniswapQuoteWithSwapQuoter } from "./_dexes/uniswap/swap-quoter"; +import { get1inchQuoteForOriginSwapExactInput } from "./_dexes/1inch"; import { InvalidParamError } from "./_errors"; +import { AMOUNT_TYPE } from "./_dexes/utils"; const SwapQuoteQueryParamsSchema = type({ swapToken: validAddress(), @@ -97,18 +98,20 @@ const handler = async ( } const swap = { - swapToken, - acrossInputToken: { + chainId: originChainId, + tokenIn: swapToken, + tokenOut: { ...acrossInputToken, chainId: originChainId, }, - swapTokenAmount, - slippage: parseFloat(swapSlippage), - }; + amount: swapTokenAmount, + slippageTolerance: parseFloat(swapSlippage), + type: AMOUNT_TYPE.EXACT_INPUT, + } as const; const quoteResults = await Promise.allSettled([ - getUniswapQuoteAndCalldata(swap), - get1inchQuoteAndCalldata(swap), + getUniswapQuoteWithSwapQuoter(swap), + get1inchQuoteForOriginSwapExactInput(swap), ]); const settledResults = quoteResults.flatMap((result) => diff --git a/api/swap/_utils.ts b/api/swap/_utils.ts new file mode 100644 index 000000000..ec14681b4 --- /dev/null +++ b/api/swap/_utils.ts @@ -0,0 +1,378 @@ +import { assert, Infer, type, string, optional, enums } from "superstruct"; +import { BigNumber, constants, utils } from "ethers"; + +import { TypedVercelRequest } from "../_types"; +import { + positiveFloatStr, + positiveIntStr, + validAddress, + boolStr, + getCachedTokenInfo, + getWrappedNativeTokenAddress, + getCachedTokenPrice, +} from "../_utils"; +import { InvalidParamError } from "../_errors"; +import { isValidIntegratorId } from "../_integrator-id"; +import { + CrossSwapFees, + CrossSwapQuotes, + SwapQuote, + Token, + AmountType, +} from "../_dexes/types"; +import { AMOUNT_TYPE } from "../_dexes/utils"; +import { encodeApproveCalldata } from "../_multicall-handler"; +import { AuthTxPayload } from "./auth/_utils"; +import { PermitTxPayload } from "./permit/_utils"; + +export const BaseSwapQueryParamsSchema = type({ + amount: positiveIntStr(), + tradeType: optional(enums(["minOutput", "exactOutput"])), + inputToken: validAddress(), + outputToken: validAddress(), + originChainId: positiveIntStr(), + destinationChainId: positiveIntStr(), + depositor: validAddress(), + recipient: optional(validAddress()), + integratorId: optional(string()), + refundAddress: optional(validAddress()), + refundOnOrigin: optional(boolStr()), + slippageTolerance: optional(positiveFloatStr(50)), // max. 50% slippage + skipOriginTxEstimation: optional(boolStr()), +}); + +export type BaseSwapQueryParams = Infer; + +export async function handleBaseSwapQueryParams( + query: TypedVercelRequest["query"] +) { + assert(query, BaseSwapQueryParamsSchema); + + const { + inputToken: _inputTokenAddress, + outputToken: _outputTokenAddress, + originChainId: _originChainId, + destinationChainId: _destinationChainId, + amount: _amount, + tradeType = AMOUNT_TYPE.MIN_OUTPUT, + recipient, + depositor, + integratorId, + refundAddress, + refundOnOrigin: _refundOnOrigin = "true", + slippageTolerance = "1", // Default to 1% slippage + skipOriginTxEstimation: _skipOriginTxEstimation = "false", + } = query; + + const originChainId = Number(_originChainId); + const destinationChainId = Number(_destinationChainId); + const refundOnOrigin = _refundOnOrigin === "true"; + const skipOriginTxEstimation = _skipOriginTxEstimation === "true"; + const isInputNative = _inputTokenAddress === constants.AddressZero; + const isOutputNative = _outputTokenAddress === constants.AddressZero; + const inputTokenAddress = isInputNative + ? getWrappedNativeTokenAddress(originChainId) + : utils.getAddress(_inputTokenAddress); + const outputTokenAddress = isOutputNative + ? getWrappedNativeTokenAddress(destinationChainId) + : utils.getAddress(_outputTokenAddress); + + if (integratorId && !isValidIntegratorId(integratorId)) { + throw new InvalidParamError({ + param: "integratorId", + message: "Invalid integrator ID. Needs to be 2 bytes hex string.", + }); + } + + if (!inputTokenAddress || !outputTokenAddress) { + throw new InvalidParamError({ + param: "inputToken, outputToken", + message: "Invalid input or output token address", + }); + } + + if (integratorId && !isValidIntegratorId(integratorId)) { + throw new InvalidParamError({ + param: "integratorId", + message: "Invalid integrator ID. Needs to be 2 bytes hex string.", + }); + } + + const amountType = tradeType as AmountType; + const amount = BigNumber.from(_amount); + + const [inputToken, outputToken] = await Promise.all([ + getCachedTokenInfo({ + address: inputTokenAddress, + chainId: originChainId, + }), + getCachedTokenInfo({ + address: outputTokenAddress, + chainId: destinationChainId, + }), + ]); + + const refundToken = refundOnOrigin ? inputToken : outputToken; + + return { + inputToken, + outputToken, + amount, + amountType, + refundOnOrigin, + integratorId, + skipOriginTxEstimation, + isInputNative, + isOutputNative, + refundAddress, + recipient, + depositor, + slippageTolerance, + refundToken, + }; +} + +export function getApprovalTxns(params: { + token: Token; + spender: string; + amount: BigNumber; +}) { + const approvalTxns: { + chainId: number; + to: string; + data: string; + }[] = []; + // USDT has a different approval flow when changing an already approve amount. + // We need to set the allowance to 0 first. + // See https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code#L201 + if (params.token.symbol === "USDT") { + approvalTxns.push({ + chainId: params.token.chainId, + to: params.token.address, + data: encodeApproveCalldata(params.spender, BigNumber.from(0)), + }); + } + approvalTxns.push({ + chainId: params.token.chainId, + to: params.token.address, + data: encodeApproveCalldata(params.spender, params.amount), + }); + return approvalTxns; +} + +async function calculateSwapFee( + swapQuote: SwapQuote, + baseCurrency: string +): Promise> { + const { tokenIn, tokenOut, expectedAmountOut, expectedAmountIn } = swapQuote; + const [inputTokenPriceBase, outputTokenPriceBase] = await Promise.all([ + getCachedTokenPrice( + tokenIn.address, + baseCurrency, + undefined, + tokenIn.chainId + ), + getCachedTokenPrice( + tokenOut.address, + baseCurrency, + undefined, + tokenOut.chainId + ), + ]); + + const normalizedIn = + parseFloat(utils.formatUnits(expectedAmountIn, tokenIn.decimals)) * + inputTokenPriceBase; + const normalizedOut = + parseFloat(utils.formatUnits(expectedAmountOut, tokenOut.decimals)) * + outputTokenPriceBase; + return { + [baseCurrency]: normalizedIn - normalizedOut, + }; +} + +async function calculateBridgeFee( + bridgeQuote: CrossSwapQuotes["bridgeQuote"], + baseCurrency: string +): Promise> { + const { inputToken, suggestedFees } = bridgeQuote; + const inputTokenPriceBase = await getCachedTokenPrice( + inputToken.address, + baseCurrency, + undefined, + inputToken.chainId + ); + const normalizedFee = + parseFloat( + utils.formatUnits(suggestedFees.totalRelayFee.total, inputToken.decimals) + ) * inputTokenPriceBase; + + return { + [baseCurrency]: normalizedFee, + }; +} + +export async function calculateCrossSwapFees( + crossSwapQuote: CrossSwapQuotes, + baseCurrency = "usd" +): Promise { + const bridgeFeePromise = calculateBridgeFee( + crossSwapQuote.bridgeQuote, + baseCurrency + ); + + const originSwapFeePromise = crossSwapQuote?.originSwapQuote + ? calculateSwapFee(crossSwapQuote.originSwapQuote, baseCurrency) + : Promise.resolve(undefined); + + const destinationSwapFeePromise = crossSwapQuote?.destinationSwapQuote + ? calculateSwapFee(crossSwapQuote.destinationSwapQuote, baseCurrency) + : Promise.resolve(undefined); + + const [bridgeFees, originSwapFees, destinationSwapFees] = await Promise.all([ + bridgeFeePromise, + originSwapFeePromise, + destinationSwapFeePromise, + ]); + + return { + bridgeFees, + originSwapFees, + destinationSwapFees, + }; +} + +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, val]) => { + if (val instanceof BigNumber) { + return [key, val.toString()]; + } + if (typeof val === "object" && val !== null) { + return [key, stringifyBigNumProps(val)]; + } + return [key, val]; + }) + ) as T; +} + +export function buildBaseSwapResponseJson(params: { + inputTokenAddress: string; + originChainId: number; + inputAmount: BigNumber; + allowance: BigNumber; + balance: BigNumber; + approvalTxns?: { + to: string; + data: string; + }[]; + originSwapQuote?: SwapQuote; + bridgeQuote: CrossSwapQuotes["bridgeQuote"]; + destinationSwapQuote?: SwapQuote; + refundToken: Token; + approvalSwapTx?: { + from: string; + to: string; + data: string; + value?: BigNumber; + gas?: BigNumber; + gasPrice: BigNumber; + }; + permitSwapTx?: AuthTxPayload | PermitTxPayload; +}) { + return stringifyBigNumProps({ + checks: { + allowance: params.approvalSwapTx + ? { + token: params.inputTokenAddress, + spender: params.approvalSwapTx.to, + actual: params.allowance, + expected: params.inputAmount, + } + : // TODO: Handle permit2 required allowance + { + token: params.inputTokenAddress, + spender: constants.AddressZero, + actual: 0, + expected: 0, + }, + balance: { + token: params.inputTokenAddress, + actual: params.balance, + expected: params.inputAmount, + }, + }, + approvalTxns: params.approvalTxns, + steps: { + originSwap: params.originSwapQuote + ? { + tokenIn: params.originSwapQuote.tokenIn, + tokenOut: params.originSwapQuote.tokenOut, + inputAmount: params.originSwapQuote.expectedAmountIn, + outputAmount: params.originSwapQuote.expectedAmountOut, + minOutputAmount: params.originSwapQuote.minAmountOut, + maxInputAmount: params.originSwapQuote.maximumAmountIn, + } + : undefined, + bridge: { + inputAmount: params.bridgeQuote.inputAmount, + outputAmount: params.bridgeQuote.outputAmount, + tokenIn: params.bridgeQuote.inputToken, + tokenOut: params.bridgeQuote.outputToken, + }, + destinationSwap: params.destinationSwapQuote + ? { + tokenIn: params.destinationSwapQuote.tokenIn, + tokenOut: params.destinationSwapQuote.tokenOut, + inputAmount: params.destinationSwapQuote.expectedAmountIn, + maxInputAmount: params.destinationSwapQuote.maximumAmountIn, + outputAmount: params.destinationSwapQuote.expectedAmountOut, + minOutputAmount: params.destinationSwapQuote.minAmountOut, + } + : undefined, + }, + refundToken: + params.refundToken.symbol === "ETH" + ? { + ...params.refundToken, + symbol: "WETH", + } + : params.refundToken, + inputAmount: + params.originSwapQuote?.expectedAmountIn ?? + params.bridgeQuote.inputAmount, + expectedOutputAmount: + params.destinationSwapQuote?.expectedAmountOut ?? + params.bridgeQuote.outputAmount, + minOutputAmount: + params.destinationSwapQuote?.minAmountOut ?? + params.bridgeQuote.outputAmount, + expectedFillTime: params.bridgeQuote.suggestedFees.estimatedFillTimeSec, + swapTx: params.approvalSwapTx + ? { + simulationSuccess: !!params.approvalSwapTx.gas, + chainId: params.originChainId, + to: params.approvalSwapTx.to, + data: params.approvalSwapTx.data, + value: params.approvalSwapTx.value, + gas: params.approvalSwapTx.gas, + gasPrice: params.approvalSwapTx.gasPrice, + } + : params.permitSwapTx + ? params.permitSwapTx.swapTx + : undefined, + eip712: params.permitSwapTx?.eip712, + }); +} diff --git a/api/swap/allowance.ts b/api/swap/allowance.ts new file mode 100644 index 000000000..488216f8e --- /dev/null +++ b/api/swap/allowance.ts @@ -0,0 +1,3 @@ +// 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..859369d9f --- /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, +} from "../../_spoke-pool-periphery"; +import { + extractDepositDataStruct, + extractSwapAndDepositDataStruct, +} 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; + + 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` + ); + } + + const swapAndDepositData = + await extractSwapAndDepositDataStruct(crossSwapQuotes); + + if (originSwapEntryPoint.name === "SpokePoolPeripheryProxy") { + const spokePoolPeripheryProxy = getSpokePoolPeripheryProxy( + originSwapEntryPoint.address, + originChainId + ); + tx = await spokePoolPeripheryProxy.populateTransaction.swapAndBridge( + swapAndDepositData + // 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, + { + ...swapAndDepositData.depositData, + exclusivityDeadline: + swapAndDepositData.depositData.exclusivityParameter, + // Typo in the contract + destinationChainid: swapAndDepositData.depositData.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` + ); + } + + const { baseDepositData } = await extractDepositDataStruct(crossSwapQuotes); + + if ( + depositEntryPoint.name === "SpokePoolPeriphery" && + crossSwapQuotes.crossSwap.isInputNative + ) { + const spokePoolPeriphery = getSpokePoolPeriphery( + depositEntryPoint.address, + originChainId + ); + tx = await spokePoolPeriphery.populateTransaction.deposit( + baseDepositData.recipient, + baseDepositData.inputToken, + baseDepositData.inputAmount, + baseDepositData.outputAmount, + baseDepositData.destinationChainId, + baseDepositData.exclusiveRelayer, + baseDepositData.quoteTimestamp, + baseDepositData.fillDeadline, + baseDepositData.exclusivityDeadline, + baseDepositData.message, + { + value: baseDepositData.inputAmount, + } + ); + toAddress = spokePoolPeriphery.address; + } else if ( + depositEntryPoint.name === "SpokePool" || + (depositEntryPoint.name === "SpokePoolPeriphery" && + !crossSwapQuotes.crossSwap.isInputNative) + ) { + const spokePool = getSpokePool(originChainId); + tx = await spokePool.populateTransaction.depositV3( + baseDepositData.depositor, + baseDepositData.recipient, + baseDepositData.inputToken, + baseDepositData.outputToken, + baseDepositData.inputAmount, + baseDepositData.outputAmount, + baseDepositData.destinationChainId, + baseDepositData.exclusiveRelayer, + baseDepositData.quoteTimestamp, + baseDepositData.fillDeadline, + baseDepositData.exclusivityDeadline, + baseDepositData.message, + { + value: crossSwapQuotes.crossSwap.isInputNative + ? baseDepositData.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..44f87c9d6 --- /dev/null +++ b/api/swap/approval/index.ts @@ -0,0 +1,168 @@ +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, + buildBaseSwapResponseJson, +} 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, + refundToken, + } = 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 responseJson = buildBaseSwapResponseJson({ + originChainId, + inputTokenAddress, + inputAmount, + approvalSwapTx: { + ...crossSwapTx, + gas: originTxGas, + gasPrice: originTxGasPrice, + }, + allowance, + balance, + approvalTxns, + originSwapQuote, + bridgeQuote, + destinationSwapQuote, + refundToken, + }); + 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/auth/_utils.ts b/api/swap/auth/_utils.ts new file mode 100644 index 000000000..81828dd40 --- /dev/null +++ b/api/swap/auth/_utils.ts @@ -0,0 +1,168 @@ +import { + CrossSwapQuotes, + DepositEntryPointContract, + OriginSwapEntryPointContract, +} from "../../_dexes/types"; +import { getReceiveWithAuthTypedData } from "../../_transfer-with-auth"; +import { + getDepositTypedData, + getSwapAndDepositTypedData, +} from "../../_spoke-pool-periphery"; +import { + extractDepositDataStruct, + extractSwapAndDepositDataStruct, +} from "../../_dexes/utils"; +import { BigNumberish, BytesLike, utils } from "ethers"; +import { SpokePoolV3PeripheryInterface } from "../../_typechain/SpokePoolV3Periphery"; + +export type AuthTxPayload = Awaited>; + +export async function buildAuthTxPayload({ + crossSwapQuotes, + authDeadline, + authStart = 0, + submissionFees, +}: { + crossSwapQuotes: CrossSwapQuotes; + authDeadline: number; + authStart?: number; + submissionFees?: { + amount: BigNumberish; + recipient: string; + }; +}) { + const { originSwapQuote, bridgeQuote, crossSwap, contracts } = + crossSwapQuotes; + const originChainId = crossSwap.inputToken.chainId; + const { originSwapEntryPoint, depositEntryPoint, originRouter } = contracts; + + let entryPointContract: + | DepositEntryPointContract + | OriginSwapEntryPointContract; + let getDepositTypedDataPromise: + | ReturnType + | ReturnType; + 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)); + + const validAfter = authStart; + const validBefore = authDeadline; + + 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` + ); + } + const swapAndDepositData = + await extractSwapAndDepositDataStruct(crossSwapQuotes); + entryPointContract = originSwapEntryPoint; + + getDepositTypedDataPromise = getSwapAndDepositTypedData({ + swapAndDepositData: swapAndDepositData, + chainId: originChainId, + }); + methodNameAndArgsWithoutSignatures = { + methodName: "swapAndBridgeWithAuthorization", + argsWithoutSignatures: { + signatureOwner: crossSwap.depositor, + swapAndDepositData, + validAfter, + validBefore, + nonce, + }, + }; + } 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}'` + ); + } + const depositDataStruct = await extractDepositDataStruct( + crossSwapQuotes, + submissionFees + ); + entryPointContract = depositEntryPoint; + getDepositTypedDataPromise = getDepositTypedData({ + depositData: depositDataStruct, + chainId: originChainId, + }); + methodNameAndArgsWithoutSignatures = { + methodName: "depositWithAuthorization", + argsWithoutSignatures: { + signatureOwner: crossSwap.depositor, + depositData: depositDataStruct, + validAfter, + validBefore, + nonce, + }, + }; + } + + const [authTypedData, depositTypedData] = await Promise.all([ + getReceiveWithAuthTypedData({ + tokenAddress: + originSwapQuote?.tokenIn.address || bridgeQuote.inputToken.address, + chainId: originChainId, + ownerAddress: crossSwap.depositor, + spenderAddress: entryPointContract.address, + value: originSwapQuote?.maximumAmountIn || bridgeQuote.inputAmount, + nonce, + validAfter, + validBefore, + }), + getDepositTypedDataPromise, + ]); + + return { + eip712: { + permit: authTypedData.eip712, + deposit: depositTypedData.eip712, + }, + swapTx: { + chainId: originChainId, + to: entryPointContract.address, + ...methodNameAndArgsWithoutSignatures, + }, + }; +} diff --git a/api/swap/auth/index.ts b/api/swap/auth/index.ts new file mode 100644 index 000000000..8af00b28b --- /dev/null +++ b/api/swap/auth/index.ts @@ -0,0 +1,143 @@ +import { VercelResponse } from "@vercel/node"; +import { assert, Infer, optional, type } from "superstruct"; +import { BigNumber } from "ethers"; + +import { TypedVercelRequest } from "../../_types"; +import { getLogger, handleErrorCondition, positiveIntStr } from "../../_utils"; +import { getCrossSwapQuotes } from "../../_dexes/cross-swap-service"; +import { + handleBaseSwapQueryParams, + BaseSwapQueryParams, + buildBaseSwapResponseJson, +} from "../_utils"; +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"; +import * as sdk from "@across-protocol/sdk"; +import { getBalance } from "../../_erc20"; + +export const authSwapQueryParamsSchema = type({ + authDeadline: optional(positiveIntStr()), +}); + +export type authSwapQueryParams = Infer; + +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 = { + 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 ?? sdk.utils.getCurrentTime()); + + 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, + refundToken, + } = 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, + // FIXME: Calculate proper fees + submissionFees: { + amount: "0", + recipient: GAS_SPONSOR_ADDRESS, + }, + }); + + const balance = await getBalance({ + chainId: inputToken.chainId, + tokenAddress: inputToken.address, + owner: crossSwapQuotes.crossSwap.depositor, + }); + + const responseJson = buildBaseSwapResponseJson({ + inputTokenAddress: inputToken.address, + originChainId: inputToken.chainId, + permitSwapTx: crossSwapTxForAuth, + inputAmount: amount, + bridgeQuote: crossSwapQuotes.bridgeQuote, + originSwapQuote: crossSwapQuotes.originSwapQuote, + destinationSwapQuote: crossSwapQuotes.destinationSwapQuote, + refundToken, + balance, + // Allowance does not matter for auth-based flows + allowance: BigNumber.from(0), + }); + + 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/api/swap/index.ts b/api/swap/index.ts new file mode 100644 index 000000000..fe0edb0ff --- /dev/null +++ b/api/swap/index.ts @@ -0,0 +1,80 @@ +import { VercelResponse } from "@vercel/node"; +import axios from "axios"; + +import { TypedVercelRequest } from "../_types"; +import { + getLogger, + handleErrorCondition, + resolveVercelEndpoint, +} from "../_utils"; +import { handleBaseSwapQueryParams, BaseSwapQueryParams } from "./_utils"; +import { getPermitArgsFromContract } from "../_permit"; +import { getReceiveWithAuthArgsFromContract } from "../_transfer-with-auth"; + +type SwapFlowType = "permit" | "transfer-with-auth" | "approval"; + +function makeSwapHandler(path: string) { + return (params: unknown) => + axios.get(`${resolveVercelEndpoint(true)}/api/swap/${path}`, { params }); +} +const swapFlowTypeToHandler = { + permit: makeSwapHandler("permit"), + "transfer-with-auth": makeSwapHandler("auth"), + approval: makeSwapHandler("approval"), +}; + +export default async function handler( + request: TypedVercelRequest, + response: VercelResponse +) { + const logger = getLogger(); + logger.debug({ + at: "Swap", + message: "Query data", + query: request.query, + }); + try { + // `/swap` only validate shared base params + const { inputToken, amount, recipient, depositor } = + await handleBaseSwapQueryParams(request.query); + + // Determine swap flow by checking if required args and methods are supported + let swapFlowType: SwapFlowType; + const args = { + tokenAddress: inputToken.address, + chainId: inputToken.chainId, + ownerAddress: depositor, + spenderAddress: recipient || depositor, + value: amount, + }; + const [permitArgsResult, transferWithAuthArgsResult] = + await Promise.allSettled([ + getPermitArgsFromContract(args), + getReceiveWithAuthArgsFromContract(args), + ]); + + if (permitArgsResult.status === "fulfilled") { + swapFlowType = "permit"; + } else if (transferWithAuthArgsResult.status === "fulfilled") { + swapFlowType = "transfer-with-auth"; + } else { + swapFlowType = "approval"; + } + + const handler = swapFlowTypeToHandler[swapFlowType]; + const { data } = await handler(request.query); + const enrichedResponseJson = { + ...data, + swapFlowType, + }; + + logger.debug({ + at: "Swap", + message: "Response data", + responseJson: enrichedResponseJson, + }); + response.status(200).json(enrichedResponseJson); + } catch (error: unknown) { + return handleErrorCondition("swap", response, logger, error); + } +} diff --git a/api/swap/permit/_utils.ts b/api/swap/permit/_utils.ts new file mode 100644 index 000000000..f42fa81b1 --- /dev/null +++ b/api/swap/permit/_utils.ts @@ -0,0 +1,152 @@ +import { BigNumberish } from "ethers"; + +import { + CrossSwapQuotes, + DepositEntryPointContract, + OriginSwapEntryPointContract, +} from "../../_dexes/types"; +import { getPermitTypedData } from "../../_permit"; +import { + getDepositTypedData, + getSwapAndDepositTypedData, +} from "../../_spoke-pool-periphery"; +import { + extractDepositDataStruct, + extractSwapAndDepositDataStruct, +} from "../../_dexes/utils"; +import { SpokePoolV3PeripheryInterface } from "../../_typechain/SpokePoolV3Periphery"; + +export type PermitTxPayload = Awaited>; + +export async function buildPermitTxPayload({ + crossSwapQuotes, + permitDeadline, + submissionFees, +}: { + crossSwapQuotes: CrossSwapQuotes; + permitDeadline: number; + submissionFees?: { + amount: BigNumberish; + recipient: string; + }; +}) { + const { originSwapQuote, bridgeQuote, crossSwap, contracts } = + crossSwapQuotes; + const originChainId = crossSwap.inputToken.chainId; + const { originSwapEntryPoint, depositEntryPoint, originRouter } = contracts; + + let entryPointContract: + | DepositEntryPointContract + | OriginSwapEntryPointContract; + let getDepositTypedDataPromise: + | ReturnType + | ReturnType; + let methodNameAndArgsWithoutSignatures: + | { + methodName: "depositWithPermit"; + argsWithoutSignatures: { + signatureOwner: string; + depositData: SpokePoolV3PeripheryInterface.DepositDataStruct; + deadline: BigNumberish; + }; + } + | { + methodName: "swapAndBridgeWithPermit"; + argsWithoutSignatures: { + signatureOwner: string; + swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct; + deadline: BigNumberish; + }; + }; + + 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` + ); + } + + const swapAndDepositData = + await extractSwapAndDepositDataStruct(crossSwapQuotes); + entryPointContract = originSwapEntryPoint; + getDepositTypedDataPromise = getSwapAndDepositTypedData({ + swapAndDepositData: swapAndDepositData, + chainId: originChainId, + }); + methodNameAndArgsWithoutSignatures = { + methodName: "swapAndBridgeWithPermit", + argsWithoutSignatures: { + signatureOwner: crossSwap.depositor, + swapAndDepositData, + deadline: permitDeadline, + }, + }; + } 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}'` + ); + } + const depositDataStruct = await extractDepositDataStruct( + crossSwapQuotes, + submissionFees + ); + entryPointContract = depositEntryPoint; + getDepositTypedDataPromise = getDepositTypedData({ + depositData: depositDataStruct, + chainId: originChainId, + }); + methodNameAndArgsWithoutSignatures = { + methodName: "depositWithPermit", + argsWithoutSignatures: { + signatureOwner: crossSwap.depositor, + depositData: depositDataStruct, + deadline: permitDeadline, + }, + }; + } + + 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: methodNameAndArgsWithoutSignatures.methodName, + argsWithoutSignatures: + methodNameAndArgsWithoutSignatures.argsWithoutSignatures, + }, + }; +} diff --git a/api/swap/permit/index.ts b/api/swap/permit/index.ts new file mode 100644 index 000000000..cdf04ece7 --- /dev/null +++ b/api/swap/permit/index.ts @@ -0,0 +1,137 @@ +import { VercelResponse } from "@vercel/node"; +import { BigNumber } from "ethers"; +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, + buildBaseSwapResponseJson, +} from "../_utils"; +import { getSwapRouter02Strategy } from "../../_dexes/uniswap/swap-router-02"; +import { InvalidParamError } from "../../_errors"; +import { buildPermitTxPayload } from "./_utils"; +import { QuoteFetchStrategies } from "../../_dexes/utils"; +import { GAS_SPONSOR_ADDRESS } from "../../relay/_utils"; +import { getBalance } from "../../_erc20"; + +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 + +// 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 +) => { + const logger = getLogger(); + logger.debug({ + at: "Swap/permit", + message: "Query data", + query: request.query, + }); + try { + // `/swap/permit` specific params validation + const { permitDeadline: _permitDeadline, ...restQuery } = request.query; + assert( + { + permitDeadline: _permitDeadline, + }, + PermitSwapQueryParamsSchema + ); + 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 { + isInputNative, + isOutputNative, + inputToken, + outputToken, + amount, + amountType, + refundOnOrigin, + refundAddress, + recipient, + depositor, + slippageTolerance, + refundToken, + } = 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 buildPermitTxPayload({ + crossSwapQuotes, + permitDeadline, + // FIXME: Calculate proper fees + submissionFees: { + amount: "0", + recipient: GAS_SPONSOR_ADDRESS, + }, + }); + + const balance = await getBalance({ + chainId: inputToken.chainId, + tokenAddress: inputToken.address, + owner: crossSwapQuotes.crossSwap.depositor, + }); + + const responseJson = buildBaseSwapResponseJson({ + inputTokenAddress: inputToken.address, + originChainId: inputToken.chainId, + permitSwapTx: crossSwapTxForPermit, + inputAmount: amount, + bridgeQuote: crossSwapQuotes.bridgeQuote, + originSwapQuote: crossSwapQuotes.originSwapQuote, + destinationSwapQuote: crossSwapQuotes.destinationSwapQuote, + refundToken, + balance, + // Allowance does not matter for permit-based flows + allowance: BigNumber.from(0), + }); + + logger.debug({ + at: "Swap/permit", + message: "Response data", + responseJson, + }); + response.status(200).json(responseJson); + } catch (error: unknown) { + return handleErrorCondition("swap/permit", response, logger, error); + } +}; + +export default handler; diff --git a/package.json b/package.json index fbdbacdcb..52ba8024c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "@across-protocol/constants": "^3.1.20", "@across-protocol/contracts": "^3.0.16", "@across-protocol/contracts-v3.0.6": "npm:@across-protocol/contracts@3.0.6", - "@across-protocol/sdk": "^3.3.22", + "@across-protocol/sdk": "^3.3.23", "@amplitude/analytics-browser": "^2.3.5", "@balancer-labs/sdk": "1.1.6-beta.16", "@emotion/react": "^11.13.0", @@ -23,8 +23,10 @@ "@sentry/react": "^7.37.2", "@tanstack/react-query": "v4", "@tanstack/react-query-devtools": "v4", - "@uniswap/sdk-core": "^4.2.0", - "@uniswap/v3-sdk": "^3.11.0", + "@uniswap/sdk-core": "^5.9.0", + "@uniswap/smart-order-router": "^4.7.8", + "@uniswap/v3-sdk": "^3.18.1", + "@upstash/qstash": "^2.7.20", "@vercel/kv": "^2.0.0", "@web3-onboard/coinbase": "^2.4.1", "@web3-onboard/core": "^2.21.2", diff --git a/scripts/generate-routes.ts b/scripts/generate-routes.ts index 1cf9f6958..27036a365 100644 --- a/scripts/generate-routes.ts +++ b/scripts/generate-routes.ts @@ -91,6 +91,7 @@ const enabledRoutes = { CHAIN_IDs.WORLD_CHAIN, ], }, + // Addresses of token-scoped `SwapAndBridge` contracts, i.e. USDC.e -> USDC swaps swapAndBridgeAddresses: { "1inch": { [CHAIN_IDs.POLYGON]: "0xaBa0F11D55C5dDC52cD0Cb2cd052B621d45159d5", @@ -102,9 +103,50 @@ const enabledRoutes = { [CHAIN_IDs.POLYGON]: "0x9220Fa27ae680E4e8D9733932128FA73362E0393", [CHAIN_IDs.OPTIMISM]: "0x6f4A733c7889f038D77D4f540182Dda17423CcbF", [CHAIN_IDs.ARBITRUM]: "0xF633b72A4C2Fb73b77A379bf72864A825aD35b6D", - // [CHAIN_IDs.BASE]: "0xbcfbCE9D92A516e3e7b0762AE218B4194adE34b4", }, }, + // Addresses of `UniversalSwapAndBridge` contracts from deployment: + // https://github.com/across-protocol/contracts/pull/731/commits/6bdbfd38f50b616ac25e49687cbac6fb6bcb543b + universalSwapAndBridgeAddresses: { + "1inch": { + [CHAIN_IDs.ARBITRUM]: "0x81C7601ac0c5825e89F967f9222B977CCD78aD77", + [CHAIN_IDs.BASE]: "0x98285D11B9F7aFec2d475805E5255f26B4490167", + [CHAIN_IDs.OPTIMISM]: "0x7631eA29479Ee265241F13FB48555A2C886d3Bf8", + [CHAIN_IDs.POLYGON]: "0xc2dcb88873e00c9d401de2cbba4c6a28f8a6e2c2", + }, + uniswap: { + [CHAIN_IDs.ARBITRUM]: "0x2414A759d4EFF700Ad81e257Ab5187d07eCeEbAb", + [CHAIN_IDs.BASE]: "0xed8b9c9aE7aCEf12eb4650d26Eb876005a4752d2", + [CHAIN_IDs.BLAST]: "0x57EE47829369e2EF62fBb423648bec70d0366204", + [CHAIN_IDs.MAINNET]: "0x0e84f089B0923EfeA51C6dF91581BFBa66A3484A", + [CHAIN_IDs.OPTIMISM]: "0x04989eaF03547E6583f9d9e42aeD11D2b78A808b", + [CHAIN_IDs.POLYGON]: "0xa55490E20057BD4775618D0FC8D51F59f602FED0", + [CHAIN_IDs.WORLD_CHAIN]: "0x56e2d1b8C7dE8D11B282E1b4C924C32D91f9102B", + [CHAIN_IDs.ZORA]: "0x75b84707e6Bf5bc48DbC3AD883c23192C869AAE4", + }, + }, + spokePoolPeripheryAddresses: { + [CHAIN_IDs.ARBITRUM]: "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + [CHAIN_IDs.BASE]: "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + [CHAIN_IDs.BLAST]: "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + [CHAIN_IDs.MAINNET]: "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + [CHAIN_IDs.OPTIMISM]: "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + [CHAIN_IDs.POLYGON]: "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + [CHAIN_IDs.WORLD_CHAIN]: "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + [CHAIN_IDs.ZK_SYNC]: "0xDFD7f7AC8F2331C4E83A43E73aB7579e736AC1Bf", + [CHAIN_IDs.ZORA]: "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + }, + spokePoolPeripheryProxyAddresses: { + [CHAIN_IDs.ARBITRUM]: "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + [CHAIN_IDs.BASE]: "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + [CHAIN_IDs.BLAST]: "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + [CHAIN_IDs.MAINNET]: "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + [CHAIN_IDs.OPTIMISM]: "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + [CHAIN_IDs.POLYGON]: "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + [CHAIN_IDs.WORLD_CHAIN]: "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + [CHAIN_IDs.ZK_SYNC]: "0x793Ff9Cd09819C537500dFcEB6F61861c1B80dCD", + [CHAIN_IDs.ZORA]: "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + }, routes: transformChainConfigs(enabledMainnetChainConfigs), }, [CHAIN_IDs.SEPOLIA]: { @@ -132,6 +174,11 @@ const enabledRoutes = { "0x17496824Ba574A4e9De80110A91207c4c63e552a", // Mocked }, }, + universalSwapAndBridgeAddresses: { + uniswap: {}, + }, + spokePoolPeripheryAddresses: {}, + spokePoolPeripheryProxyAddresses: {}, routes: transformChainConfigs(enabledSepoliaChainConfigs), }, } as const; @@ -354,20 +401,20 @@ async function generateRoutes(hubPoolChainId = 1) { ), merkleDistributorAddress: utils.getAddress(config.merkleDistributorAddress), claimAndStakeAddress: utils.getAddress(config.claimAndStakeAddress), - swapAndBridgeAddresses: Object.entries( - config.swapAndBridgeAddresses - ).reduce( - (acc, [key, value]) => ({ - ...acc, - [key]: Object.entries(value).reduce( - (acc, [chainId, address]) => ({ - ...acc, - [chainId]: utils.getAddress(address as string), - }), - {} - ), - }), - {} + swapAndBridgeAddresses: checksumAddressesOfNestedMap( + config.swapAndBridgeAddresses as Record> + ), + universalSwapAndBridgeAddresses: checksumAddressesOfNestedMap( + config.universalSwapAndBridgeAddresses 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) @@ -622,4 +669,23 @@ 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]: checksumAddressOfMap(value), + }), + {} + ); +} + generateRoutes(Number(process.argv[2])); diff --git a/scripts/tests/_swap-utils.ts b/scripts/tests/_swap-utils.ts new file mode 100644 index 000000000..d5fcdaea1 --- /dev/null +++ b/scripts/tests/_swap-utils.ts @@ -0,0 +1,280 @@ +import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; +import { ethers, Wallet } from "ethers"; +import dotenv from "dotenv"; +import axios from "axios"; + +import { buildBaseSwapResponseJson } from "../../api/swap/_utils"; + +dotenv.config(); + +export type BaseSwapResponse = Awaited< + ReturnType +>; + +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("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, + }, + }, +]; +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; +} + +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); + 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${slug ? `/${slug}` : ""}`, + { + params: testCase.params, + } + ); + console.log(JSON.stringify(response.data, null, 2)); + return response.data as BaseSwapResponse; + } +} + +export async function signAndWaitPermitFlow(params: { + wallet: Wallet; + swapResponse: BaseSwapResponse; +}) { + if (!params.swapResponse.eip712) { + throw new Error("No EIP712 data for permit"); + } + + // sign permit + deposit + const permitSig = await params.wallet._signTypedData( + params.swapResponse.eip712.permit.domain, + params.swapResponse.eip712.permit.types, + params.swapResponse.eip712.permit.message + ); + console.log("Signed permit:", permitSig); + + const depositSig = await params.wallet._signTypedData( + params.swapResponse.eip712.deposit.domain, + params.swapResponse.eip712.deposit.types, + params.swapResponse.eip712.deposit.message + ); + console.log("Signed deposit:", depositSig); + + // relay + const relayResponse = await axios.post(`${SWAP_API_BASE_URL}/api/relay`, { + ...params.swapResponse.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)); + } +} + +export async function signAndWaitAllowanceFlow(params: { + wallet: Wallet; + swapResponse: BaseSwapResponse; +}) { + if (!params.swapResponse.swapTx || !("data" in params.swapResponse.swapTx)) { + throw new Error("No swap tx for allowance flow"); + } + + if (params.swapResponse.approvalTxns) { + console.log("Approval needed..."); + let step = 1; + for (const approvalTxn of params.swapResponse.approvalTxns) { + const stepLabel = `(${step}/${params.swapResponse.approvalTxns.length})`; + const tx = await params.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 params.wallet.sendTransaction({ + to: params.swapResponse.swapTx.to, + data: params.swapResponse.swapTx.data, + value: params.swapResponse.swapTx.value, + gasLimit: params.swapResponse.swapTx.gas, + gasPrice: params.swapResponse.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 new file mode 100644 index 000000000..cde9dc469 --- /dev/null +++ b/scripts/tests/swap-allowance.ts @@ -0,0 +1,31 @@ +import { Wallet } from "ethers"; + +import { getProvider } from "../../api/_utils"; +import { fetchSwapQuote, signAndWaitAllowanceFlow } from "./_swap-utils"; + +async function swapWithAllowance() { + console.log("Swapping with allowance..."); + const swapQuote = await fetchSwapQuote("approval"); + + if (!swapQuote || !swapQuote.swapTx || !("data" in swapQuote.swapTx)) { + console.log("No swap quote with tx data for approval"); + return; + } + + if (process.env.DEV_WALLET_PK) { + const wallet = new Wallet(process.env.DEV_WALLET_PK!).connect( + getProvider(swapQuote.swapTx.chainId) + ); + + await signAndWaitAllowanceFlow({ wallet, swapResponse: swapQuote }); + } +} + +swapWithAllowance() + .then(() => console.log("Done")) + .catch((e) => { + console.error(e); + if (e.response?.data) { + console.log("Tx for debug sim:", e.response.data.transaction); + } + }); diff --git a/scripts/tests/swap-auth.ts b/scripts/tests/swap-auth.ts new file mode 100644 index 000000000..b03b066f0 --- /dev/null +++ b/scripts/tests/swap-auth.ts @@ -0,0 +1,31 @@ +import { Wallet } from "ethers"; + +import { getProvider } from "../../api/_utils"; +import { fetchSwapQuote, signAndWaitPermitFlow } from "./_swap-utils"; + +async function swapWithAuth() { + console.log("Swapping with auth..."); + const swapQuote = await fetchSwapQuote("auth"); + + if (!swapQuote || !swapQuote.swapTx || !swapQuote.eip712) { + console.log("No swap quote with EIP712 data for auth"); + return; + } + + if (process.env.DEV_WALLET_PK) { + const wallet = new Wallet(process.env.DEV_WALLET_PK!).connect( + getProvider(swapQuote.swapTx.chainId) + ); + + await signAndWaitPermitFlow({ wallet, swapResponse: swapQuote }); + } +} + +swapWithAuth() + .then(() => console.log("Done")) + .catch((e) => { + console.error(e); + if (e.response?.data) { + console.log("Tx for debug sim:", e.response.data.transaction); + } + }); diff --git a/scripts/tests/swap-permit.ts b/scripts/tests/swap-permit.ts new file mode 100644 index 000000000..8a3c13ed0 --- /dev/null +++ b/scripts/tests/swap-permit.ts @@ -0,0 +1,31 @@ +import { Wallet } from "ethers"; + +import { getProvider } from "../../api/_utils"; +import { fetchSwapQuote, signAndWaitPermitFlow } from "./_swap-utils"; + +async function swapWithPermit() { + console.log("Swapping with permit..."); + const swapQuote = await fetchSwapQuote("permit"); + + if (!swapQuote || !swapQuote.swapTx || !swapQuote.eip712) { + console.log("No swap quote with EIP712 data for permit"); + return; + } + + if (process.env.DEV_WALLET_PK) { + const wallet = new Wallet(process.env.DEV_WALLET_PK!).connect( + getProvider(swapQuote.swapTx.chainId) + ); + + await signAndWaitPermitFlow({ wallet, swapResponse: swapQuote }); + } +} + +swapWithPermit() + .then(() => console.log("Done")) + .catch((e) => { + console.error(e); + if (e.response?.data) { + console.log("Tx for debug sim:", e.response.data.transaction); + } + }); diff --git a/scripts/tests/swap-unified.ts b/scripts/tests/swap-unified.ts new file mode 100644 index 000000000..fcd14fad2 --- /dev/null +++ b/scripts/tests/swap-unified.ts @@ -0,0 +1,45 @@ +import { Wallet } from "ethers"; + +import { getProvider } from "../../api/_utils"; +import { + fetchSwapQuote, + signAndWaitAllowanceFlow, + signAndWaitPermitFlow, +} from "./_swap-utils"; + +async function swapUnified() { + console.log("Swapping with unified endpoint..."); + const swapQuote = await fetchSwapQuote(); + + if (!swapQuote || !swapQuote.swapTx) { + console.log("No swap quote"); + return; + } + + if (process.env.DEV_WALLET_PK) { + const wallet = new Wallet(process.env.DEV_WALLET_PK!).connect( + getProvider(swapQuote.swapTx.chainId) + ); + + // if a permit-based flow is available, the unified endpoint will prefer that over an + // allowance-based flow and return the relevant EIP712 data. + if (swapQuote.eip712) { + // sign permit + relay + track + await signAndWaitPermitFlow({ wallet, swapResponse: swapQuote }); + } + // if no permit-based flow is available, we can use the allowance-based flow + else { + // sign and send approval txns + swap txn + await signAndWaitAllowanceFlow({ wallet, swapResponse: swapQuote }); + } + } +} + +swapUnified() + .then(() => console.log("Done")) + .catch((e) => { + console.error(e); + if (e.response?.data) { + console.log("Tx for debug sim:", e.response.data.transaction); + } + }); diff --git a/src/data/routes_11155111_0x14224e63716afAcE30C9a417E0542281869f7d9e.json b/src/data/routes_11155111_0x14224e63716afAcE30C9a417E0542281869f7d9e.json index 2bf848111..0a883bab8 100644 --- a/src/data/routes_11155111_0x14224e63716afAcE30C9a417E0542281869f7d9e.json +++ b/src/data/routes_11155111_0x14224e63716afAcE30C9a417E0542281869f7d9e.json @@ -13,6 +13,11 @@ "11155420": "0x17496824Ba574A4e9De80110A91207c4c63e552a" } }, + "universalSwapAndBridgeAddresses": { + "uniswap": {} + }, + "spokePoolPeripheryAddresses": {}, + "spokePoolPeripheryProxyAddresses": {}, "routes": [ { "fromChain": 11155111, diff --git a/src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json b/src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json index e527eeb0e..65db5d3c8 100644 --- a/src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json +++ b/src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json @@ -20,6 +20,46 @@ "42161": "0xF633b72A4C2Fb73b77A379bf72864A825aD35b6D" } }, + "universalSwapAndBridgeAddresses": { + "1inch": { + "10": "0x7631eA29479Ee265241F13FB48555A2C886d3Bf8", + "137": "0xC2dCB88873E00c9d401De2CBBa4C6A28f8A6e2c2", + "8453": "0x98285D11B9F7aFec2d475805E5255f26B4490167", + "42161": "0x81C7601ac0c5825e89F967f9222B977CCD78aD77" + }, + "uniswap": { + "1": "0x0e84f089B0923EfeA51C6dF91581BFBa66A3484A", + "10": "0x04989eaF03547E6583f9d9e42aeD11D2b78A808b", + "137": "0xa55490E20057BD4775618D0FC8D51F59f602FED0", + "480": "0x56e2d1b8C7dE8D11B282E1b4C924C32D91f9102B", + "8453": "0xed8b9c9aE7aCEf12eb4650d26Eb876005a4752d2", + "42161": "0x2414A759d4EFF700Ad81e257Ab5187d07eCeEbAb", + "81457": "0x57EE47829369e2EF62fBb423648bec70d0366204", + "7777777": "0x75b84707e6Bf5bc48DbC3AD883c23192C869AAE4" + } + }, + "spokePoolPeripheryAddresses": { + "1": "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + "10": "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + "137": "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + "324": "0xDFD7f7AC8F2331C4E83A43E73aB7579e736AC1Bf", + "480": "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + "8453": "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + "42161": "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + "81457": "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3", + "7777777": "0xED7Bf315Ba2E9Db86b766b8AaC48502298dfe7d3" + }, + "spokePoolPeripheryProxyAddresses": { + "1": "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + "10": "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + "137": "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + "324": "0x793Ff9Cd09819C537500dFcEB6F61861c1B80dCD", + "480": "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + "8453": "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + "42161": "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + "81457": "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8", + "7777777": "0x2d5E44b66bD40267fb816c9537E026545bEbbAC8" + }, "routes": [ { "fromChain": 1, diff --git a/src/utils/bridge.ts b/src/utils/bridge.ts index ead10f840..136c71af9 100644 --- a/src/utils/bridge.ts +++ b/src/utils/bridge.ts @@ -331,20 +331,6 @@ export async function sendSwapAndBridgeTx( ); } - const [_swapTokenAddress, _acrossInputTokenAddress] = await Promise.all([ - swapAndBridge.SWAP_TOKEN(), - swapAndBridge.ACROSS_INPUT_TOKEN(), - ]); - - if ( - swapTokenAddress.toLowerCase() !== _swapTokenAddress.toLowerCase() || - inputTokenAddress.toLowerCase() !== _acrossInputTokenAddress.toLowerCase() - ) { - throw new Error( - `Mismatch between the SwapAndBridge contract's swap token and input token addresses` - ); - } - const inputAmount = BigNumber.from(swapQuote.minExpectedInputTokenAmount); const outputAmount = inputAmount.sub( inputAmount.mul(relayerFeePct).div(fixedPointAdjustment) diff --git a/vercel.json b/vercel.json index 00a7f48ec..9ac06f42b 100644 --- a/vercel.json +++ b/vercel.json @@ -20,6 +20,9 @@ }, "api/cron-ping-endpoints.ts": { "maxDuration": 90 + }, + "api/relay/jobs/process.ts": { + "maxDuration": 90 } }, "rewrites": [ diff --git a/yarn.lock b/yarn.lock index 82366d99a..e31430a44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,16 +16,11 @@ "@uma/common" "^2.17.0" hardhat "^2.9.3" -"@across-protocol/constants@^3.1.19", "@across-protocol/constants@^3.1.20": +"@across-protocol/constants@^3.1.19", "@across-protocol/constants@^3.1.20", "@across-protocol/constants@^3.1.9": version "3.1.20" resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.20.tgz#305bd41f5644b7db5d9fd12a6a6b4bbbbe2fd016" integrity sha512-B5RsvuOQsZdFgLk0WcFZGmoivm6g6gv95a+YKVBydcxZkNxAsyP065UQEDAmvRXvPhqGyehhd52515Xa/3bzyg== -"@across-protocol/constants@^3.1.9": - version "3.1.13" - resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.13.tgz#b4caf494e9d9fe50290cca91b7883ea408fdb90a" - integrity sha512-EsTJgQL5p+XXs40aBxOSbMOpQr/f4ty+Iyget8Oh6MT/cncCa2+W8a78fbqYqTtSpH6Sm7E8nvT8gPuSS6ef1w== - "@across-protocol/contracts-v3.0.6@npm:@across-protocol/contracts@3.0.6": version "3.0.6" resolved "https://registry.yarnpkg.com/@across-protocol/contracts/-/contracts-3.0.6.tgz#7508fc52db50cfa670edc7c4e9e615ad25cdd56c" @@ -83,10 +78,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^3.3.22": - version "3.3.22" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.3.22.tgz#d71a88fcf239141a170aec12e7c8dd05f9c8405a" - integrity sha512-+YIJm5SU8tjWWwD8TNsJRoHwJ2PhyhwggfAX6CP3dSHsEza8r8T9c4ijV6oTmIvZRxQo3rTMSzLkrCh/tQGoCg== +"@across-protocol/sdk@^3.3.23": + version "3.3.23" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.3.23.tgz#ecc638895f33425f5905d95f1680a76504ad66ec" + integrity sha512-1AVVE8Z3rCjPDu/HAqAe2sErQXm+vbBz7VuVZPVmzdlLAHKAaAFOqtmWUNJg3iPYAkfAAjfoykBT48APDWRkfg== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.20" @@ -107,7 +102,6 @@ superstruct "^0.15.4" tslib "^2.6.2" viem "^2.21.15" - viem "^2.21.15" "@adraffy/ens-normalize@1.10.0": version "1.10.0" @@ -124,11 +118,6 @@ resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== -"@adraffy/ens-normalize@^1.10.1": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" - integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== - "@amplitude/ampli@^1.34.0": version "1.34.0" resolved "https://registry.yarnpkg.com/@amplitude/ampli/-/ampli-1.34.0.tgz#878bdeddc392ff24d707f8137834f9213098dd8e" @@ -1342,13 +1331,6 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.25.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" - integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/template@^7.22.15", "@babel/template@^7.22.5", "@babel/template@^7.3.3": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -1477,40 +1459,6 @@ bn.js "^5.1.2" buffer-layout "^1.2.0" -"@coral-xyz/anchor-errors@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@coral-xyz/anchor-errors/-/anchor-errors-0.30.1.tgz#bdfd3a353131345244546876eb4afc0e125bec30" - integrity sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ== - -"@coral-xyz/anchor@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.30.1.tgz#17f3e9134c28cd0ea83574c6bab4e410bcecec5d" - integrity sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ== - dependencies: - "@coral-xyz/anchor-errors" "^0.30.1" - "@coral-xyz/borsh" "^0.30.1" - "@noble/hashes" "^1.3.1" - "@solana/web3.js" "^1.68.0" - bn.js "^5.1.2" - bs58 "^4.0.1" - buffer-layout "^1.2.2" - camelcase "^6.3.0" - cross-fetch "^3.1.5" - crypto-hash "^1.3.0" - eventemitter3 "^4.0.7" - pako "^2.0.3" - snake-case "^3.0.4" - superstruct "^0.15.4" - toml "^3.0.0" - -"@coral-xyz/borsh@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.30.1.tgz#869d8833abe65685c72e9199b8688477a4f6b0e3" - integrity sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ== - dependencies: - bn.js "^5.1.2" - buffer-layout "^1.2.0" - "@dabh/diagnostics@^2.0.2": version "2.0.3" resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" @@ -2401,7 +2349,7 @@ bufio "^1.0.7" chai "^4.3.4" -"@eth-optimism/core-utils@0.13.2": +"@eth-optimism/core-utils@^0.13.2": version "0.13.2" resolved "https://registry.yarnpkg.com/@eth-optimism/core-utils/-/core-utils-0.13.2.tgz#c0187c3abf6d86dad039edf04ff81299253214fe" integrity sha512-u7TOKm1RxH1V5zw7dHmfy91bOuEAZU68LT/9vJPkuWEjaTl+BgvPDRDTurjzclHzN0GbWdcpOqPZg4ftjkJGaw== @@ -2434,13 +2382,13 @@ ethers "^5.5.4" lodash "^4.17.21" -"@eth-optimism/sdk@^3.3.1": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@eth-optimism/sdk/-/sdk-3.3.1.tgz#f72b6f93b58e2a2943f10aca3be91dfc23d9839f" - integrity sha512-zf8qL+KwYWUUwvdcjF1HpBxgKSt5wsKr8oa6jwqUhdPkQHUtVK5SRKtqXqYplnAgKtxDQYwlK512GU16odEl1w== +"@eth-optimism/sdk@^3.2.2", "@eth-optimism/sdk@^3.3.1": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@eth-optimism/sdk/-/sdk-3.3.2.tgz#68a5f6e77f9c85f6eeb9db8044b3b69199520eb0" + integrity sha512-+zhxT0YkBIEzHsuIayQGjr8g9NawZo6/HYfzg1NSEFsE2Yt0NyCWqVDFTuuak0T6AvIa2kNcl3r0Z8drdb2QmQ== dependencies: "@eth-optimism/contracts" "0.6.0" - "@eth-optimism/core-utils" "0.13.2" + "@eth-optimism/core-utils" "^0.13.2" lodash "^4.17.21" merkletreejs "^0.3.11" rlp "^2.2.7" @@ -2507,7 +2455,7 @@ "@ethersproject/properties" "^5.5.0" "@ethersproject/strings" "^5.5.0" -"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.12", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.6.3", "@ethersproject/abi@^5.7.0": +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.6.3", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== @@ -3919,13 +3867,6 @@ dependencies: "@noble/hashes" "1.5.0" -"@noble/curves@1.6.0", "@noble/curves@^1.4.0", "@noble/curves@^1.4.2", "@noble/curves@^1.6.0", "@noble/curves@~1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" - integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== - dependencies: - "@noble/hashes" "1.5.0" - "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" @@ -3946,11 +3887,6 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== -"@noble/hashes@1.5.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.5.0", "@noble/hashes@~1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" - integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== - "@noble/hashes@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" @@ -4343,11 +4279,21 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.1.0.tgz#baec89a7f5f73e3d8ea582a78f1980134b605375" integrity sha512-TihZitscnaHNcZgXGj9zDLDyCqjziytB4tMCwXq0XimfWkAjBYyk5/pOsDbbwcavhlc79HhpTEpQcrMnPVa1mw== +"@openzeppelin/contracts@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.0.tgz#3092d70ea60e3d1835466266b1d68ad47035a2d5" + integrity sha512-52Qb+A1DdOss8QvJrijYYPSf32GUg2pGaG/yCxtaA3cu4jduouTdg4XZSMLW9op54m1jH7J8hoajhHKOPsoJFw== + "@openzeppelin/contracts@4.9.6", "@openzeppelin/contracts@^4.2.0", "@openzeppelin/contracts@^4.7.3", "@openzeppelin/contracts@^4.8.1": version "4.9.6" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.6.tgz#2a880a24eb19b4f8b25adc2a5095f2aa27f39677" integrity sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA== +"@openzeppelin/contracts@5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.2.tgz#b1d03075e49290d06570b2fd42154d76c2a5d210" + integrity sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA== + "@phenomnomnominal/tsquery@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@phenomnomnominal/tsquery/-/tsquery-3.0.0.tgz#6f2f4dbf6304ff52b12cc7a5b979f20c3794a22a" @@ -4982,11 +4928,6 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== -"@scure/base@~1.1.7", "@scure/base@~1.1.8": - version "1.1.9" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" - integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== - "@scure/bip32@1.1.5": version "1.1.5" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.1.5.tgz#d2ccae16dcc2e75bc1d75f5ef3c66a338d1ba300" @@ -5023,15 +4964,6 @@ "@noble/hashes" "~1.5.0" "@scure/base" "~1.1.7" -"@scure/bip32@1.5.0", "@scure/bip32@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.5.0.tgz#dd4a2e1b8a9da60e012e776d954c4186db6328e6" - integrity sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw== - dependencies: - "@noble/curves" "~1.6.0" - "@noble/hashes" "~1.5.0" - "@scure/base" "~1.1.7" - "@scure/bip39@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5" @@ -5064,14 +4996,6 @@ "@noble/hashes" "~1.5.0" "@scure/base" "~1.1.8" -"@scure/bip39@1.4.0", "@scure/bip39@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.4.0.tgz#664d4f851564e2e1d4bffa0339f9546ea55960a6" - integrity sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw== - dependencies: - "@noble/hashes" "~1.5.0" - "@scure/base" "~1.1.8" - "@sentry/browser@7.37.2": version "7.37.2" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.37.2.tgz#355dd28ad12677d63e0b12c5209d12b3f98ac3a4" @@ -5463,9 +5387,9 @@ buffer "^6.0.3" "@solana/web3.js@^1.31.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.95.2": - version "1.95.4" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.4.tgz#771603f60d75cf7556ad867e1fd2efae32f9ad09" - integrity sha512-sdewnNEA42ZSMxqkzdwEWi6fDgzwtJHaQa5ndUGEJYtoOnM6X5cvPmjoTUp7/k7bRrVAxfBgDnvQQHD6yhlLYw== + version "1.95.5" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.5.tgz#ba70da4c205c64249ed94369fe2d617c0347cd85" + integrity sha512-hU9cBrbg1z6gEjLH9vwIckGBVB78Ijm0iZFNk4ocm5OD82piPwuk3MeQ1rfiKD9YQtr95krrcaopb49EmQJlRg== dependencies: "@babel/runtime" "^7.25.0" "@noble/curves" "^1.4.2" @@ -6419,13 +6343,6 @@ dependencies: tslib "^2.8.0" -"@swc/helpers@^0.5.11": - version "0.5.15" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7" - integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== - dependencies: - tslib "^2.8.0" - "@synthetixio/ethereum-wallet-mock@0.0.1-alpha.7": version "0.0.1-alpha.7" resolved "https://registry.yarnpkg.com/@synthetixio/ethereum-wallet-mock/-/ethereum-wallet-mock-0.0.1-alpha.7.tgz#313abfab9176d9da71ec69c40a96fbcd334f6a42" @@ -6870,6 +6787,13 @@ "@types/connect" "*" "@types/node" "*" +"@types/brotli@^1.3.4": + version "1.3.4" + resolved "https://registry.yarnpkg.com/@types/brotli/-/brotli-1.3.4.tgz#3eefc5493218a99141771f351142dd640efde5d8" + integrity sha512-cKYjgaS2DMdCKF7R0F5cgx1nfBYObN2ihIuPGQ4/dlIY6RpV7OWNwe9L8V4tTVKL2eZqOkNM9FM/rgTvLf4oXw== + dependencies: + "@types/node" "*" + "@types/cacheable-request@^6.0.1", "@types/cacheable-request@^6.0.2": version "6.0.3" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" @@ -6906,13 +6830,6 @@ dependencies: "@types/node" "*" -"@types/connect@^3.4.33": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - "@types/cross-spawn@^6.0.2": version "6.0.4" resolved "https://registry.yarnpkg.com/@types/cross-spawn/-/cross-spawn-6.0.4.tgz#e658d29e2308a01f48b7b30fd8cdf07aeb2e5a82" @@ -7219,7 +7136,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== -"@types/node@^12.0.0", "@types/node@^12.12.54", "@types/node@^12.12.6": "@types/node@^12.0.0", "@types/node@^12.12.54", "@types/node@^12.12.6": version "12.20.55" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" @@ -7425,11 +7341,6 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== -"@types/uuid@^8.3.4": - version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" - integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== - "@types/webpack-env@^1.17.0": version "1.18.0" resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.18.0.tgz#ed6ecaa8e5ed5dfe8b2b3d00181702c9925f13fb" @@ -7449,20 +7360,6 @@ dependencies: "@types/node" "*" -"@types/ws@^7.4.4": - version "7.4.7" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" - integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== - dependencies: - "@types/node" "*" - -"@types/ws@^8.2.2": - version "8.5.13" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.13.tgz#6414c280875e2691d0d1e080b05addbf5cb91e20" - integrity sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA== - dependencies: - "@types/node" "*" - "@types/ws@^8.5.5": version "8.5.5" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb" @@ -7489,13 +7386,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yargs@^17.0.33": - version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" - integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== - dependencies: - "@types/yargs-parser" "*" - "@types/yargs@^17.0.8": version "17.0.24" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" @@ -7739,6 +7629,11 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== +"@uniswap/default-token-list@^11.13.0": + version "11.19.0" + resolved "https://registry.yarnpkg.com/@uniswap/default-token-list/-/default-token-list-11.19.0.tgz#12d4e40f6c79f794d3e3a71e2d4d9784fb6c967b" + integrity sha512-H/YLpxeZUrzT4Ki8mi4k5UiadREiLHg7WUqCv0Qt/VkOjX2mIBhrxCj1Wh61/J7lK0XqOjksfpm6RG1+YErPoQ== + "@uniswap/lib@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-1.1.1.tgz#0afd29601846c16e5d082866cbb24a9e0758e6bc" @@ -7749,19 +7644,74 @@ resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-4.0.1-alpha.tgz#2881008e55f075344675b3bca93f020b028fbd02" integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA== -"@uniswap/sdk-core@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-4.2.0.tgz#9930f133baec9c1118d891ebf8fcba7f7efc153d" - integrity sha512-yXAMLHZRYYuh6KpN2nOlLTYBjGiopmI9WUB4Z0tyNkW4ZZub54cUt22eibpGbZAhRAMxclox9IPIs6wwrM3soQ== +"@uniswap/permit2-sdk@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@uniswap/permit2-sdk/-/permit2-sdk-1.3.0.tgz#b54124e570f0adbaca9d39b2de3054fd7d3798a1" + integrity sha512-LstYQWP47dwpQrgqBJ+ysFstne9LgI5FGiKHc2ewjj91MTY8Mq1reocu6U/VDncdR5ef30TUOcZ7gPExRY8r6Q== + dependencies: + ethers "^5.7.0" + tiny-invariant "^1.1.0" + +"@uniswap/router-sdk@^1.14.0", "@uniswap/router-sdk@^1.14.2": + version "1.14.3" + resolved "https://registry.yarnpkg.com/@uniswap/router-sdk/-/router-sdk-1.14.3.tgz#99bf2cb8a90073f00d6195ccc56e9eb2b21b2123" + integrity sha512-dYD0WdPvZoezPkNWGjOrgilnshdKN88ljricaY/9Zyu0FTd9KizdOqcP9wLPLPWXLyFDsVrEQqNHjJWf3ILeJw== + dependencies: + "@ethersproject/abi" "^5.5.0" + "@uniswap/sdk-core" "^5.8.0" + "@uniswap/swap-router-contracts" "^1.3.0" + "@uniswap/v2-sdk" "^4.6.0" + "@uniswap/v3-sdk" "^3.17.0" + "@uniswap/v4-sdk" "^1.10.3" + +"@uniswap/sdk-core@^5.0.0", "@uniswap/sdk-core@^5.3.1", "@uniswap/sdk-core@^5.8.0", "@uniswap/sdk-core@^5.8.1", "@uniswap/sdk-core@^5.8.2", "@uniswap/sdk-core@^5.9.0": + version "5.9.0" + resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-5.9.0.tgz#8f1edf4d0e94b314f4394fa5abe0bf5fc9c5a79a" + integrity sha512-OME7WR6+5QwQs45A2079r+/FS0zU944+JCQwUX9GyIriCxqw2pGu4F9IEqmlwD+zSIMml0+MJnJJ47pFgSyWDw== dependencies: "@ethersproject/address" "^5.0.2" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/strings" "5.7.0" big.js "^5.2.2" decimal.js-light "^2.5.0" jsbi "^3.1.4" tiny-invariant "^1.1.0" toformat "^2.0.0" -"@uniswap/swap-router-contracts@^1.2.1": +"@uniswap/smart-order-router@^4.7.8": + version "4.7.8" + resolved "https://registry.yarnpkg.com/@uniswap/smart-order-router/-/smart-order-router-4.7.8.tgz#4f07f9fdb057fe3403da5d0b573f6209ffa67e45" + integrity sha512-rMVpBL7oqPU2z6RWR4V9QToT2l6pJgeGI/JRd/rowFltnjh0VOsW/cAkWrSi6wVeEnxG7pwT+8GcxkyA0qhudQ== + dependencies: + "@eth-optimism/sdk" "^3.2.2" + "@types/brotli" "^1.3.4" + "@uniswap/default-token-list" "^11.13.0" + "@uniswap/permit2-sdk" "^1.3.0" + "@uniswap/router-sdk" "^1.14.0" + "@uniswap/sdk-core" "^5.9.0" + "@uniswap/swap-router-contracts" "^1.3.1" + "@uniswap/token-lists" "^1.0.0-beta.31" + "@uniswap/universal-router" "^1.6.0" + "@uniswap/universal-router-sdk" "^4.6.1" + "@uniswap/v2-sdk" "^4.6.1" + "@uniswap/v3-sdk" "^3.17.1" + "@uniswap/v4-sdk" "^1.10.0" + async-retry "^1.3.1" + await-timeout "^1.1.1" + axios "^0.21.1" + brotli "^1.3.3" + bunyan "^1.8.15" + bunyan-blackhole "^1.1.1" + ethers "^5.7.2" + graphql "^15.5.0" + graphql-request "^3.4.0" + lodash "^4.17.21" + mnemonist "^0.38.3" + node-cache "^5.1.2" + stats-lite "^2.2.0" + +"@uniswap/swap-router-contracts@^1.3.0", "@uniswap/swap-router-contracts@^1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@uniswap/swap-router-contracts/-/swap-router-contracts-1.3.1.tgz#0ebbb93eb578625618ed9489872de381f9c66fb4" integrity sha512-mh/YNbwKb7Mut96VuEtL+Z5bRe0xVIbjjiryn+iMMrK2sFKhR4duk/86mEz0UO5gSx4pQIw9G5276P5heY/7Rg== @@ -7773,12 +7723,53 @@ dotenv "^14.2.0" hardhat-watcher "^2.1.1" +"@uniswap/token-lists@^1.0.0-beta.31": + version "1.0.0-beta.34" + resolved "https://registry.yarnpkg.com/@uniswap/token-lists/-/token-lists-1.0.0-beta.34.tgz#879461f5d4009327a24259bbab797e0f22db58c8" + integrity sha512-Hc3TfrFaupg0M84e/Zv7BoF+fmMWDV15mZ5s8ZQt2qZxUcNw2GQW+L6L/2k74who31G+p1m3GRYbJpAo7d1pqA== + +"@uniswap/universal-router-sdk@^4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@uniswap/universal-router-sdk/-/universal-router-sdk-4.6.1.tgz#0d4aa108b8d0cbef7878015c6c4d40a62220c6ee" + integrity sha512-HfPYd8xz3/Sjb8if/wAPFOzj0+m7173PVLy+VzF4HrbBwDreU0amYHwf4zQBjffXmys2QtOdwfu7BfZrUwx0kQ== + dependencies: + "@openzeppelin/contracts" "4.7.0" + "@uniswap/permit2-sdk" "^1.3.0" + "@uniswap/router-sdk" "^1.14.2" + "@uniswap/sdk-core" "^5.8.2" + "@uniswap/universal-router" "2.0.0-beta.2" + "@uniswap/v2-core" "^1.0.1" + "@uniswap/v2-sdk" "^4.6.0" + "@uniswap/v3-core" "1.0.0" + "@uniswap/v3-sdk" "^3.18.1" + "@uniswap/v4-sdk" "^1.10.0" + bignumber.js "^9.0.2" + ethers "^5.7.0" + +"@uniswap/universal-router@2.0.0-beta.2": + version "2.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@uniswap/universal-router/-/universal-router-2.0.0-beta.2.tgz#0f891c4772733c356465d8ed2f76a43989ec9092" + integrity sha512-/USVkWZrOCjLeZluR7Yk8SpfWDUKG/MLcOyuxuwnqM1xCJj5ekguSYhct+Yfo/3t9fsZcnL8vSYgz0MKqAomGg== + dependencies: + "@openzeppelin/contracts" "5.0.2" + "@uniswap/v2-core" "1.0.1" + "@uniswap/v3-core" "1.0.0" + +"@uniswap/universal-router@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@uniswap/universal-router/-/universal-router-1.6.0.tgz#3d7372e98a0303c70587802ee6841b8b6b42fc6f" + integrity sha512-Gt0b0rtMV1vSrgXY3vz5R1RCZENB+rOkbOidY9GvcXrK1MstSrQSOAc+FCr8FSgsDhmRAdft0lk5YUxtM9i9Lg== + dependencies: + "@openzeppelin/contracts" "4.7.0" + "@uniswap/v2-core" "1.0.1" + "@uniswap/v3-core" "1.0.0" + "@uniswap/v2-core@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.0.tgz#e0fab91a7d53e8cafb5326ae4ca18351116b0844" integrity sha512-BJiXrBGnN8mti7saW49MXwxDBRFiWemGetE58q8zgfnPPzQKq55ADltEILqOt6VFZ22kVeVKbF8gVd8aY3l7pA== -"@uniswap/v2-core@^1.0.1": +"@uniswap/v2-core@1.0.1", "@uniswap/v2-core@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== @@ -7791,6 +7782,17 @@ "@uniswap/lib" "1.1.1" "@uniswap/v2-core" "1.0.0" +"@uniswap/v2-sdk@^4.6.0", "@uniswap/v2-sdk@^4.6.1": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@uniswap/v2-sdk/-/v2-sdk-4.6.2.tgz#6267f66b6c2dc2e9363a89925673aa01aada9a56" + integrity sha512-6VrXfbq4XAVTUle4jH9jfKRk2ZaOOClDrL+HPB4jzIVgoKt45hhWjZLkCPxfVahr/OFTAaOTFCIt47Le41PYKw== + dependencies: + "@ethersproject/address" "^5.0.2" + "@ethersproject/solidity" "^5.0.9" + "@uniswap/sdk-core" "^5.9.0" + tiny-invariant "^1.1.0" + tiny-warning "^1.0.3" + "@uniswap/v3-core@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.0.tgz#6c24adacc4c25dceee0ba3ca142b35adbd7e359d" @@ -7812,15 +7814,29 @@ "@uniswap/v3-core" "^1.0.0" base64-sol "1.0.1" -"@uniswap/v3-sdk@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.11.0.tgz#328309fbafddd8c618b7b6850bb99cacf6733a79" - integrity sha512-gz6Q6SlN34AXvxhyz181F90D4OuIkxLnzBAucEzB9Fv3Z+3orHZY/SpGaD02nP1VsNQVu/DQvOsdkPUDGn1Y9Q== +"@uniswap/v3-sdk@3.12.0": + version "3.12.0" + resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.12.0.tgz#2d819aa777578b747c880e0bc86a9718354140c5" + integrity sha512-mUCg9HLKl20h6W8+QtELqN/uaO47/KDSf+EOht+W3C6jt2eGuzSANqS2CY7i8MsAsnZ+MjPhmN+JTOIvf7azfA== dependencies: - "@ethersproject/abi" "^5.0.12" + "@ethersproject/abi" "^5.5.0" "@ethersproject/solidity" "^5.0.9" - "@uniswap/sdk-core" "^4.2.0" - "@uniswap/swap-router-contracts" "^1.2.1" + "@uniswap/sdk-core" "^5.0.0" + "@uniswap/swap-router-contracts" "^1.3.0" + "@uniswap/v3-periphery" "^1.1.1" + "@uniswap/v3-staker" "1.0.0" + tiny-invariant "^1.1.0" + tiny-warning "^1.0.3" + +"@uniswap/v3-sdk@^3.17.0", "@uniswap/v3-sdk@^3.17.1", "@uniswap/v3-sdk@^3.18.1": + version "3.18.1" + resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.18.1.tgz#cb714b252336ba662a3298c0525f6668101b0fef" + integrity sha512-TGrKLToSWwfx6VV2d7fh4kwQMlgspXTLE49ep5zfYODVVqV6WhrRdbteHb3e0bjdjxGSj0gzoLmhsjmoJTE1/g== + dependencies: + "@ethersproject/abi" "^5.5.0" + "@ethersproject/solidity" "^5.0.9" + "@uniswap/sdk-core" "^5.8.1" + "@uniswap/swap-router-contracts" "^1.3.0" "@uniswap/v3-periphery" "^1.1.1" "@uniswap/v3-staker" "1.0.0" tiny-invariant "^1.1.0" @@ -7835,6 +7851,26 @@ "@uniswap/v3-core" "1.0.0" "@uniswap/v3-periphery" "^1.0.1" +"@uniswap/v4-sdk@^1.10.0", "@uniswap/v4-sdk@^1.10.3": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@uniswap/v4-sdk/-/v4-sdk-1.11.0.tgz#10087014f09f795aeb0372949198052f29d946e4" + integrity sha512-j7ZehPr+gKtAwD8ojtvefwYSIRatU9XCTCCWcQDHeOSEPiy63ijq4Nq7jtIvzdaq1BlvIB3qVzTI4V0mz6Wq9g== + dependencies: + "@ethersproject/solidity" "^5.0.9" + "@uniswap/sdk-core" "^5.3.1" + "@uniswap/v3-sdk" "3.12.0" + tiny-invariant "^1.1.0" + tiny-warning "^1.0.3" + +"@upstash/qstash@^2.7.20": + version "2.7.20" + resolved "https://registry.yarnpkg.com/@upstash/qstash/-/qstash-2.7.20.tgz#7027829be8f2087d35ab179ec6c9bd9ee5154a64" + integrity sha512-fdh5df4BuJwSUhXv33g/Eaj7gG+fu3tBoXyGE5hg4zEZX9fwUY/nuc0uDlLWh54L66JfMdESVOjIaiLa1cgL1Q== + dependencies: + crypto-js ">=4.2.0" + jose "^5.2.3" + neverthrow "^7.0.1" + "@upstash/redis@^1.31.3": version "1.34.0" resolved "https://registry.yarnpkg.com/@upstash/redis/-/redis-1.34.0.tgz#f32cd53ebeeafbba7eca10f8597a573d5a2fed0d" @@ -8503,14 +8539,6 @@ JSONStream@^1.3.5: jsonparse "^1.2.0" through ">=2.2.7 <3" -JSONStream@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - abab@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" @@ -8548,11 +8576,6 @@ abitype@1.0.6, abitype@^1.0.6: resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.6.tgz#76410903e1d88e34f1362746e2d407513c38565b" integrity sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A== -abitype@1.0.6, abitype@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.6.tgz#76410903e1d88e34f1362746e2d407513c38565b" - integrity sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A== - abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -8671,13 +8694,6 @@ agentkeepalive@^4.5.0: dependencies: humanize-ms "^1.2.1" -agentkeepalive@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" - integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== - dependencies: - humanize-ms "^1.2.1" - aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -9172,7 +9188,7 @@ async-mutex@^0.5.0: dependencies: tslib "^2.4.0" -async-retry@^1.3.3: +async-retry@^1.3.1, async-retry@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== @@ -9191,12 +9207,7 @@ async@^2.0.1, async@^2.1.2, async@^2.4.0, async@^2.5.0: dependencies: lodash "^4.17.14" -async@^3.2.0, async@^3.2.3: - version "3.2.4" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" - integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== - -async@^3.2.5: +async@^3.2.0, async@^3.2.3, async@^3.2.5: version "3.2.5" resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== @@ -9233,6 +9244,11 @@ await-semaphore@^0.1.3: resolved "https://registry.yarnpkg.com/await-semaphore/-/await-semaphore-0.1.3.tgz#2b88018cc8c28e06167ae1cdff02504f1f9688d3" integrity sha512-d1W2aNSYcz/sxYO4pMGX9vq65qOTu0P800epMud+6cYYX0QcT7zyqcxec3VWzpgvdXo57UWmVbZpLMjX2m1I7Q== +await-timeout@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/await-timeout/-/await-timeout-1.1.1.tgz#d42062ee6bc4eb271fe4d4f851eb658dae7e3906" + integrity sha512-gsDXAS6XVc4Jt+7S92MPX6Noq69bdeXUPEaXd8dk3+yVr629LTDLxNt4j1ycBbrU+AStK2PhKIyNIM+xzWMVOQ== + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -9282,16 +9298,7 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" -axios@^1.5.1, axios@^1.6.0, axios@^1.6.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" - integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== - dependencies: - follow-redirects "^1.15.6" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - -axios@^1.7.4: +axios@^1.5.1, axios@^1.6.0, axios@^1.6.2, axios@^1.7.4: version "1.7.7" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== @@ -10010,7 +10017,7 @@ base-x@^5.0.0: resolved "https://registry.yarnpkg.com/base-x/-/base-x-5.0.0.tgz#6d835ceae379130e1a4cb846a70ac4746f28ea9b" integrity sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ== -base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: +base64-js@^1.1.2, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -10084,13 +10091,6 @@ bigint-buffer@^1.1.5: dependencies: bindings "^1.3.0" -bigint-buffer@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" - integrity sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA== - dependencies: - bindings "^1.3.0" - bignumber.js@*, bignumber.js@^9.0.0, bignumber.js@^9.0.1, bignumber.js@^9.0.2: version "9.1.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" @@ -10124,7 +10124,6 @@ binary@~0.3.0: buffers "~0.1.1" chainsaw "~0.1.0" -bindings@^1.3.0, bindings@^1.5.0: bindings@^1.3.0, bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" @@ -10250,15 +10249,6 @@ borsh@^0.7.0: bs58 "^4.0.0" text-encoding-utf-8 "^1.0.2" -borsh@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" - integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== - dependencies: - bn.js "^5.2.0" - bs58 "^4.0.0" - text-encoding-utf-8 "^1.0.2" - bowser@^2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" @@ -10351,6 +10341,13 @@ brorand@^1.0.1, brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== +brotli@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/brotli/-/brotli-1.3.3.tgz#7365d8cc00f12cf765d2b2c898716bcf4b604d48" + integrity sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg== + dependencies: + base64-js "^1.1.2" + browser-assert@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/browser-assert/-/browser-assert-1.2.1.tgz#9aaa5a2a8c74685c2ae05bfe46efd606f068c200" @@ -10466,13 +10463,6 @@ bs58@^6.0.0: dependencies: base-x "^5.0.0" -bs58@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-6.0.0.tgz#a2cda0130558535dd281a2f8697df79caaf425d8" - integrity sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw== - dependencies: - base-x "^5.0.0" - bs58check@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" @@ -10537,11 +10527,6 @@ buffer-layout@^1.2.0, buffer-layout@^1.2.2: resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5" integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA== -buffer-layout@^1.2.0, buffer-layout@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5" - integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA== - buffer-reverse@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-reverse/-/buffer-reverse-1.0.1.tgz#49283c8efa6f901bc01fa3304d06027971ae2f60" @@ -10557,7 +10542,6 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== -buffer@6.0.3, buffer@^6.0.1, buffer@^6.0.3, buffer@~6.0.3: buffer@6.0.3, buffer@^6.0.1, buffer@^6.0.3, buffer@~6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" @@ -10598,6 +10582,23 @@ bundle-require@^4.0.0: dependencies: load-tsconfig "^0.2.3" +bunyan-blackhole@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/bunyan-blackhole/-/bunyan-blackhole-1.1.1.tgz#b9208586dc0b4e47f4f713215b1bddd65e4f6257" + integrity sha512-UwzNPhbbSqbzeJhCbygqjlAY7p0ZUdv1ADXPQvDh3CA7VW3C/rCc1gaQO/8j9QL4vsKQCQZQSQIEwX+lxioPAQ== + dependencies: + stream-blackhole "^1.0.3" + +bunyan@^1.8.15: + version "1.8.15" + resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.15.tgz#8ce34ca908a17d0776576ca1b2f6cbd916e93b46" + integrity sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig== + optionalDependencies: + dtrace-provider "~0.8" + moment "^2.19.3" + mv "~2" + safe-json-stringify "~1" + busboy@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" @@ -10720,7 +10721,6 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0, camelcase@^6.2.0, camelcase@^6.3.0: camelcase@^6.0.0, camelcase@^6.2.0, camelcase@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" @@ -11286,16 +11286,16 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +clone@2.x, clone@^2.0.0, clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -clone@^2.0.0, clone@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== - clsx@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" @@ -11419,12 +11419,6 @@ commander@^12.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== -commander@^2.11.0, commander@^2.15.0, commander@^2.19.0, commander@^2.20.3: -commander@^12.1.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" - integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== - commander@^2.11.0, commander@^2.15.0, commander@^2.19.0, commander@^2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -11713,13 +11707,6 @@ cross-fetch@^3.1.5: dependencies: node-fetch "^2.6.12" -cross-fetch@^3.1.5: - version "3.1.8" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" - integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== - dependencies: - node-fetch "^2.6.12" - cross-fetch@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" @@ -11792,12 +11779,7 @@ crypto-hash@^1.3.0: resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247" integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg== -crypto-hash@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247" - integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg== - -crypto-js@^4.2.0: +crypto-js@>=4.2.0, crypto-js@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== @@ -12125,11 +12107,6 @@ delay@^5.0.0: resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== -delay@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" - integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -12346,14 +12323,6 @@ dot-case@^3.0.4: no-case "^3.0.4" tslib "^2.0.3" -dot-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" - integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - dot-prop@^5.0.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -12402,6 +12371,13 @@ drbg.js@^1.0.1: create-hash "^1.1.2" create-hmac "^1.1.4" +dtrace-provider@~0.8: + version "0.8.8" + resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e" + integrity sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg== + dependencies: + nan "^2.14.0" + duplexer2@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -12806,7 +12782,6 @@ es6-iterator@^2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-promise@4.2.8, es6-promise@^4.0.3, es6-promise@^4.2.8: es6-promise@4.2.8, es6-promise@^4.0.3, es6-promise@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" @@ -12819,13 +12794,6 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== - dependencies: - es6-promise "^4.0.3" - es6-symbol@^3.1.1, es6-symbol@^3.1.3: version "3.1.4" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.4.tgz#f4e7d28013770b4208ecbf3e0bf14d3bcb557b8c" @@ -13710,7 +13678,7 @@ ethers@5.5.3: "@ethersproject/web" "5.5.1" "@ethersproject/wordlists" "5.5.0" -ethers@5.7.2, ethers@^5.0.13, ethers@^5.4.2, ethers@^5.5.4, ethers@^5.6.8, ethers@^5.7.1, ethers@^5.7.2: +ethers@5.7.2, ethers@^5.0.13, ethers@^5.4.2, ethers@^5.5.4, ethers@^5.6.8, ethers@^5.7.0, ethers@^5.7.1, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -13807,11 +13775,6 @@ eventemitter3@5.0.1, eventemitter3@^5.0.1: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== -eventemitter3@5.0.1, eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - eventemitter3@^4.0.0, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" @@ -14044,11 +14007,6 @@ eyes@^0.1.8: resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== -eyes@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" - integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== - fake-merkle-patricia-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/fake-merkle-patricia-tree/-/fake-merkle-patricia-tree-1.0.1.tgz#4b8c3acfb520afadf9860b1f14cd8ce3402cddd3" @@ -14109,11 +14067,6 @@ fast-stable-stringify@^1.0.0: resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313" integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== -fast-stable-stringify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313" - integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== - fast-text-encoding@^1.0.0, fast-text-encoding@^1.0.3: version "1.0.6" resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867" @@ -14944,6 +14897,17 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^6.0.1: + version "6.0.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" + integrity sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A== + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.0.0, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -15194,7 +15158,7 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== -graphql-request@^3.3.0, graphql-request@^3.5.0: +graphql-request@^3.3.0, graphql-request@^3.4.0, graphql-request@^3.5.0: version "3.7.0" resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.7.0.tgz#c7406e537084f8b9788541e3e6704340ca13055b" integrity sha512-dw5PxHCgBneN2DDNqpWu8QkbbJ07oOziy8z+bK/TAXufsOLaETuVO4GkXrbs0WjhdKhBMN3BkpN/RIvUHkmNUQ== @@ -15210,10 +15174,10 @@ graphql-tag@^2.11.0: dependencies: tslib "^2.1.0" -graphql@^15.4.0, graphql@^15.6.1: - version "15.8.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" - integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== +graphql@^15.4.0, graphql@^15.5.0, graphql@^15.6.1: + version "15.9.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.9.0.tgz#4e8ca830cfd30b03d44d3edd9cac2b0690304b53" + integrity sha512-GCOQdvm7XxV1S4U4CGrsdlEN37245eC8P9zaYCMr6K1BG0IPGy5lUwmJsEOGyl1GD6HXjOtl2keCP9asRBwNvA== growl@1.10.5: version "1.10.5" @@ -15742,13 +15706,6 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" - husky@^8.0.0: version "8.0.1" resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.1.tgz#511cb3e57de3e3190514ae49ed50f6bc3f50b3e9" @@ -16653,6 +16610,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isnumber@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isnumber/-/isnumber-1.0.0.tgz#0e3f9759b581d99dd85086f0ec2a74909cfadd01" + integrity sha512-JLiSz/zsZcGFXPrB4I/AGBvtStkt+8QmksyZBZnVXnnK9XdTEyz0tX8CRYljtwYDuIuZzih6DpHQdi+3Q6zHPw== + iso-constants@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/iso-constants/-/iso-constants-0.1.2.tgz#3d2456ed5aeaa55d18564f285ba02a47a0d885b4" @@ -16706,11 +16668,6 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== -isomorphic-ws@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" - integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== - isows@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.3.tgz#93c1cf0575daf56e7120bab5c8c448b0809d0d74" @@ -16726,11 +16683,6 @@ isows@1.0.6: resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== -isows@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" - integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -16913,24 +16865,6 @@ jayson@^4.1.1: uuid "^8.3.2" ws "^7.5.10" -jayson@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.2.tgz#443c26a8658703e0b2e881117b09395d88b6982e" - integrity sha512-5nzMWDHy6f+koZOuYsArh2AXs73NfWYVlFyJJuCedr93GpY+Ku8qq10ropSXVfHK+H0T6paA88ww+/dV+1fBNA== - dependencies: - "@types/connect" "^3.4.33" - "@types/node" "^12.12.54" - "@types/ws" "^7.4.4" - JSONStream "^1.3.5" - commander "^2.20.3" - delay "^5.0.0" - es6-promisify "^5.0.0" - eyes "^0.1.8" - isomorphic-ws "^4.0.1" - json-stringify-safe "^5.0.1" - uuid "^8.3.2" - ws "^7.5.10" - jest-changed-files@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" @@ -17349,11 +17283,6 @@ jinx-rust@0.1.6: resolved "https://registry.yarnpkg.com/jinx-rust/-/jinx-rust-0.1.6.tgz#c7bce55d97bfbad76a9b930c01fe6a8629a170d7" integrity sha512-qP+wtQL1PrDDFwtPKhNGtjWOmijCrKdfUHWTV2G/ikxfjrh+cjdvkQTmny9RAsVF0jiui9m+F0INWu4cuRcZeQ== -jinx-rust@0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/jinx-rust/-/jinx-rust-0.1.6.tgz#c7bce55d97bfbad76a9b930c01fe6a8629a170d7" - integrity sha512-qP+wtQL1PrDDFwtPKhNGtjWOmijCrKdfUHWTV2G/ikxfjrh+cjdvkQTmny9RAsVF0jiui9m+F0INWu4cuRcZeQ== - joi@17.9.1: version "17.9.1" resolved "https://registry.yarnpkg.com/joi/-/joi-17.9.1.tgz#74899b9fa3646904afa984a11df648eca66c9018" @@ -17370,6 +17299,11 @@ jose@^4.9.3: resolved "https://registry.yarnpkg.com/jose/-/jose-4.9.3.tgz#890abd3f26725fe0f2aa720bc2f7835702b624db" integrity sha512-f8E/z+T3Q0kA9txzH2DKvH/ds2uggcw0m3vVPSB9HrSkrQ7mojjifvS7aR8cw+lQl2Fcmx9npwaHpM/M3GD8UQ== +jose@^5.2.3: + version "5.9.6" + resolved "https://registry.yarnpkg.com/jose/-/jose-5.9.6.tgz#77f1f901d88ebdc405e57cce08d2a91f47521883" + integrity sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ== + joycon@^3.0.1: version "3.1.1" resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" @@ -17621,7 +17555,6 @@ json-stable-stringify@^1.0.1: jsonify "^0.0.1" object-keys "^1.1.1" -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -17689,11 +17622,6 @@ jsonparse@^1.2.0: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== - jsonschema@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" @@ -17736,16 +17664,7 @@ jws@^4.0.0: jwa "^2.0.0" safe-buffer "^5.0.1" -keccak@^3.0.0, keccak@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.3.tgz#4bc35ad917be1ef54ff246f904c2bbbf9ac61276" - integrity sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ== - dependencies: - node-addon-api "^2.0.0" - node-gyp-build "^4.2.0" - readable-stream "^3.6.0" - -keccak@^3.0.3: +keccak@^3.0.0, keccak@^3.0.2, keccak@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d" integrity sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q== @@ -18336,13 +18255,6 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" -lower-case@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" - integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== - dependencies: - tslib "^2.0.3" - lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -18870,7 +18782,7 @@ mkdirp@*: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== -mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^0.5.5: +mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -18882,7 +18794,7 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mnemonist@^0.38.0: +mnemonist@^0.38.0, mnemonist@^0.38.3: version "0.38.5" resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.5.tgz#4adc7f4200491237fe0fa689ac0b86539685cade" integrity sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg== @@ -18951,7 +18863,7 @@ mock-fs@^4.1.0: resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" integrity sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw== -moment@^2.22.1, moment@^2.24.0: +moment@^2.19.3, moment@^2.22.1, moment@^2.24.0: version "2.30.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== @@ -18983,7 +18895,6 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.0.0, ms@^2.1.1: ms@2.1.3, ms@^2.0.0, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -19155,6 +19066,15 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +mv@~2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2" + integrity sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg== + dependencies: + mkdirp "~0.5.1" + ncp "~2.0.0" + rimraf "~2.4.0" + mz@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -19236,6 +19156,11 @@ natural-orderby@^2.0.1: resolved "https://registry.yarnpkg.com/natural-orderby/-/natural-orderby-2.0.3.tgz#8623bc518ba162f8ff1cdb8941d74deb0fdcc016" integrity sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q== +ncp@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" + integrity sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA== + negotiator@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" @@ -19246,6 +19171,11 @@ neo-async@^2.5.0, neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +neverthrow@^7.0.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/neverthrow/-/neverthrow-7.2.0.tgz#76fa0a6cf1f6d59f0770df461c92b8b270910694" + integrity sha512-iGBUfFB7yPczHHtA8dksKTJ9E8TESNTAx1UQWW6TzMF280vo9jdPYpLUXrMN1BCkPdHFdNG3fxOt2CUad8KhAw== + next-tick@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" @@ -19271,14 +19201,6 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -no-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" - integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== - dependencies: - lower-case "^2.0.2" - tslib "^2.0.3" - node-addon-api@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" @@ -19289,6 +19211,13 @@ node-addon-api@^5.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== +node-cache@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d" + integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg== + dependencies: + clone "2.x" + node-dir@^0.1.17: version "0.1.17" resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" @@ -19323,7 +19252,6 @@ node-fetch@^1.0.1, node-fetch@~1.7.1: encoding "^0.1.11" is-stream "^1.0.1" -node-fetch@^2.0.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7, node-fetch@^2.6.9, node-fetch@^2.7.0: node-fetch@^2.0.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7, node-fetch@^2.6.9, node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" @@ -19758,19 +19686,6 @@ ox@0.1.2: abitype "^1.0.6" eventemitter3 "5.0.1" -ox@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ox/-/ox-0.1.2.tgz#0f791be2ccabeaf4928e6d423498fe1c8094e560" - integrity sha512-ak/8K0Rtphg9vnRJlbOdaX9R7cmxD2MiSthjWGaQdMk3D7hrAlDoM+6Lxn7hN52Za3vrXfZ7enfke/5WjolDww== - dependencies: - "@adraffy/ens-normalize" "^1.10.1" - "@noble/curves" "^1.6.0" - "@noble/hashes" "^1.5.0" - "@scure/bip32" "^1.5.0" - "@scure/bip39" "^1.4.0" - abitype "^1.0.6" - eventemitter3 "5.0.1" - p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -19875,11 +19790,6 @@ pako@^2.0.3: resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== -pako@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" - integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== - pako@~0.2.0: version "0.2.9" resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" @@ -20475,20 +20385,11 @@ prettier-plugin-rust@^0.1.9: jinx-rust "0.1.6" prettier "^2.7.1" -prettier-plugin-rust@^0.1.9: - version "0.1.9" - resolved "https://registry.yarnpkg.com/prettier-plugin-rust/-/prettier-plugin-rust-0.1.9.tgz#1a93b035743fa02a006b4980a1035a260ea9e501" - integrity sha512-n1DTTJQaHMdnoG/+nKUvBm3EKsMVWsYES2UPCiOPiZdBrmuAO/pX++m7L3+Hz3uuhtddpH0HRKHB2F3jbtJBOQ== - dependencies: - jinx-rust "0.1.6" - prettier "^2.7.1" - prettier@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a" integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA== -prettier@^2.7.1, prettier@^2.8.0: prettier@^2.7.1, prettier@^2.8.0: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" @@ -21774,6 +21675,13 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rimraf@~2.4.0: + version "2.4.5" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da" + integrity sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ== + dependencies: + glob "^6.0.1" + rimraf@~2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -21889,22 +21797,6 @@ rpc-websockets@^9.0.2: bufferutil "^4.0.1" utf-8-validate "^5.0.2" -rpc-websockets@^9.0.2: - version "9.0.4" - resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.0.4.tgz#9d8ee82533b5d1e13d9ded729e3e38d0d8fa083f" - integrity sha512-yWZWN0M+bivtoNLnaDbtny4XchdAIF5Q4g/ZsC5UC61Ckbp0QczwO8fg44rV3uYmY4WHd+EZQbn90W1d8ojzqQ== - dependencies: - "@swc/helpers" "^0.5.11" - "@types/uuid" "^8.3.4" - "@types/ws" "^8.2.2" - buffer "^6.0.3" - eventemitter3 "^5.0.1" - uuid "^8.3.2" - ws "^8.5.0" - optionalDependencies: - bufferutil "^4.0.1" - utf-8-validate "^5.0.2" - run-async@^2.4.0, run-async@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -21977,6 +21869,11 @@ safe-event-emitter@^1.0.1: dependencies: events "^3.0.0" +safe-json-stringify@~1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" + integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg== + safe-json-utils@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/safe-json-utils/-/safe-json-utils-1.1.1.tgz#0e883874467d95ab914c3f511096b89bfb3e63b1" @@ -22456,14 +22353,6 @@ snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" -snake-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" - integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== - dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" - snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -22768,6 +22657,13 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" +stats-lite@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/stats-lite/-/stats-lite-2.2.0.tgz#278a5571fa1d2e8b1691295dccc0235282393bbf" + integrity sha512-/Kz55rgUIv2KP2MKphwYT/NCuSfAlbbMRv2ZWw7wyXayu230zdtzhxxuXXcvsc6EmmhS8bSJl3uS1wmMHFumbA== + dependencies: + isnumber "~1.0.0" + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" @@ -22797,6 +22693,11 @@ storybook@^7.5.3: dependencies: "@storybook/cli" "7.5.3" +stream-blackhole@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/stream-blackhole/-/stream-blackhole-1.0.3.tgz#6fc2e2c2e9d9fde6be8c68d3db88de09802e4d63" + integrity sha512-7NWl3dkmCd12mPkEwTbBPGxwvxj7L4O9DTjJudn02Fmk9K+RuPaDF8zeGo3kmjbsffU5E1aGpZ1dTR9AaRg6AQ== + stream-buffers@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-3.0.2.tgz#5249005a8d5c2d00b3a32e6e0a6ea209dc4f3521" @@ -22865,7 +22766,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -22900,6 +22801,15 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -22974,7 +22884,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -23002,6 +22912,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -23117,11 +23034,6 @@ superstruct@^2.0.2: resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54" integrity sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A== -superstruct@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54" - integrity sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A== - supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.0: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" @@ -23373,11 +23285,6 @@ text-encoding-utf-8@^1.0.2: resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== -text-encoding-utf-8@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" - integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== - text-hex@1.0.x: version "1.0.0" resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" @@ -23434,7 +23341,6 @@ through2@^2.0.3: readable-stream "~2.3.6" xtend "~4.0.1" -"through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8: "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -23595,11 +23501,6 @@ toml@^3.0.0: resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== -toml@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" - integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== - tough-cookie@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" @@ -23777,11 +23678,6 @@ tslib@^2.8.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== -tslib@^2.8.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - tsort@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/tsort/-/tsort-0.0.1.tgz#e2280f5e817f8bf4275657fd0f9aebd44f5a2786" @@ -24473,9 +24369,9 @@ viem@^1.0.0: ws "8.13.0" viem@^2.21.15: - version "2.21.45" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.45.tgz#7a445428d4909cc334f231ee916ede1b69190603" - integrity sha512-I+On/IiaObQdhDKWU5Rurh6nf3G7reVkAODG5ECIfjsrGQ3EPJnxirUPT4FNV6bWER5iphoG62/TidwuTSOA1A== + version "2.21.48" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.48.tgz#f8f1d0bf5381282e22e6a1f8b72ebd6e64426480" + integrity sha512-/hBHyG1gdIIuiQv0z9YmzXl5eWJa0UCZGwkeuQzH2Bmg6FIEwZeEcxgiytXZydip+p2wMBFa1jdr7o5O1+mrIg== dependencies: "@noble/curves" "1.6.0" "@noble/hashes" "1.5.0" @@ -25499,14 +25395,6 @@ webauthn-p256@0.0.10: "@noble/curves" "^1.4.0" "@noble/hashes" "^1.4.0" -webauthn-p256@0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.10.tgz#877e75abe8348d3e14485932968edf3325fd2fdd" - integrity sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA== - dependencies: - "@noble/curves" "^1.4.0" - "@noble/hashes" "^1.4.0" - webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -25737,7 +25625,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -25772,6 +25660,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -25833,11 +25730,6 @@ ws@8.18.0, ws@^8.5.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== -ws@8.18.0, ws@^8.5.0: - version "8.18.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" - integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== - ws@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" @@ -25871,11 +25763,6 @@ ws@^7.5.10: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^7.5.10: - version "7.5.10" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" - integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== - ws@^8.11.0, ws@^8.13.0, ws@^8.2.3: version "8.17.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea"