UNPKG

@syncswap/sdk

Version:

SyncSwap TypeScript SDK for building DeFi applications

958 lines 45.6 kB
import { BigNumber, } from "ethers"; import { ContractInitiator } from "./contracts/contractInitiator.js"; import { ensureTokenRegistered, TokenRegistry, getRouteTokens, getWETHAddress } from "./tokens/tokenRegistry.js"; import { filterPathsToUseExactIn, findAllPossiblePaths, findBestAmountsForPathsExactIn, getRoutePools, SwapRouter, } from "./router/index.js"; import { PoolTypes, } from "./router/types.js"; import { createOverrides, generateSplitPermitParams, getAmountMin, getBlockTimestamp, isETH, LINEA_MAINNET, SCROLL_MAINNET, shouldUseV2RouterInterface, SOPHON_MAINNET, ZKSYNC_MAINNET, } from "./sdkHelper.js"; import { ZERO, ZERO_ADDRESS, UINT256_MAX } from "./utils/constants.js"; import ContractRegistry from "./contracts/contractRegistry.js"; import { Token } from "./tokens/token.js"; import invariant from "tiny-invariant"; import { defaultAbiCoder } from "ethers/lib/utils.js"; import { setStateStore, stateStore } from "./statestore/statestore.js"; import ContractLoader from "./contracts/contractLoader.js"; import { ContractTypes } from "./contracts/contractTypes.js"; import { apiFetchAllPools } from "./api/index.js"; import { getCachedMulticall3Contract } from "./router/helper.js"; export * from "./sdkHelper.js"; // EIP-712 Types for Permit const PERMIT_TYPES = { Permit: [ { name: "owner", type: "address" }, { name: "spender", type: "address" }, { name: "value", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, ], }; // ERC20 Permit function signatures const ERC20_PERMIT_FUNCTIONS = { NONCES: "0x7ecebe00", // bytes4(keccak256("nonces(address)")) NAME: "0x06fdde03", // bytes4(keccak256("name()")) VERSION: "0x54fd4d50", // bytes4(keccak256("version()")) }; let swapRouteCache = null; const OVERRIDE_DEADLINE = BigNumber.from(12000); // 120 mins as zkSync timestamp is the L1 block export default class SyncSwapSDK { constructor(config) { this.initialized = false; // Cache for token permit data this.tokenPermitCache = new Map(); this.config = config; this.updateConfig(config); } async updateConfig(config) { const currentState = stateStore(); const updates = {}; // Only include properties that are explicitly provided in config if (config.network !== undefined) { updates.network = config.network; if (this.initialized) { await this.initialize(currentState.providerOrSigner); } } if (config.allowUniswapV3Pools !== undefined) { updates.allowUniswapV3Pools = config.allowUniswapV3Pools; } if (config.aquaPoolOnly !== undefined) { updates.aquaPoolOnly = config.aquaPoolOnly; } if (config.enablePaymaster !== undefined) { updates.allowSponsoredPaymaster = config.enablePaymaster; if (config.network === SOPHON_MAINNET) { updates.enableSophonPaymaster = config.enablePaymaster; } } else { updates.allowSponsoredPaymaster = false; updates.enableSophonPaymaster = false; } if (config.enableHops !== undefined) { updates.enableHops = config.enableHops; } if (config.enableSplits !== undefined) { updates.enableSplits = config.enableSplits; } if (config.slidePage !== undefined) { updates.userSettings = { ...currentState.userSettings, slippage: config.slidePage || "auto", }; } // Only update state if there are actual changes if (Object.keys(updates).length > 0) { setStateStore(updates); } } async initialize(provider) { if (this.initialized) { return; } await this.setProviderOrSigner(provider); // initialize smart contracts await this.initializeSyncSwapSDKContracts(); // initialize default token list (Remove if example UI is not included) await TokenRegistry.initialize(this.config.network); await this.fetchAllPools(); this.initialized = true; } async initializeSyncSwapSDKContracts() { await ContractInitiator.initialize(this.config.network); } // fetch all pools and register token async fetchAllPools() { const networkName = stateStore().network; const account = stateStore().account; const allPools = await apiFetchAllPools(networkName, account); allPools?.pools.forEach((pool) => { ensureTokenRegistered(pool.token0); ensureTokenRegistered(pool.token1); }); } async setProviderOrSigner(providerOrSigner) { setStateStore({ providerOrSigner: providerOrSigner, }); // Try to get account address if it's a signer try { if ('getAddress' in providerOrSigner && typeof providerOrSigner.getAddress === 'function') { const account = await providerOrSigner.getAddress(); setStateStore({ account: account, }); } else { // It's a provider only, set a placeholder account for read-only operations setStateStore({ account: "0x0000000000000000000000000000000000000000", }); } } catch (error) { console.error("Error getting account address", error); // Set placeholder account for read-only operations setStateStore({ account: "0x0000000000000000000000000000000000000000", }); } } async fetchSyncSwapRouteData(tokenAddressA, tokenAddressB, userAccount, routeCommonBaseTokenAddresses) { return await getRoutePools(userAccount ?? "0x0000000000000000000000000000000000000001", tokenAddressA, tokenAddressB, routeCommonBaseTokenAddresses ?? getRouteTokens()); } // calculate a swap route with given route data, input token and input amount // get route amount out results, this function supports exact input amount only async calculateSyncSwapRoute(routePools, tokenAddressIn, tokenAddressOut, tokenAmountIn) { if (!routePools) { throw new Error("Route pools not found"); } setStateStore({ currentRoutePools: routePools, }); let _isETH = false; let _isETHOut = false; if (isETH(tokenAddressIn)) { tokenAddressIn = getWETHAddress(); _isETH = true; } if (isETH(tokenAddressOut)) { _isETHOut = true; } // cache if (swapRouteCache !== null) { if (swapRouteCache.routePools === routePools && swapRouteCache.tokenIn === tokenAddressIn && swapRouteCache.amount.eq(tokenAmountIn) && swapRouteCache.enableHops === stateStore().enableHops && swapRouteCache.enableSplits === stateStore().enableSplits && swapRouteCache.aquaPoolOnly === stateStore().aquaPoolOnly) { return swapRouteCache.cachedRoute; } } const ts = Date.now(); SwapRouter.latestRouteTimestamp = ts; // 1. find all viable paths for swapping const paths = findAllPossiblePaths(tokenAddressIn, ts, tokenAmountIn); const tokenOut = routePools.tokenA == tokenAddressIn ? routePools.tokenB : routePools.tokenA; const defaultData = { tokenIn: tokenAddressIn, tokenOut: tokenOut, found: false, amountIn: tokenAmountIn, amountOut: ZERO, quote: ZERO, paths: [], priceImpact: ZERO, gas: ZERO, }; console.log("tokenOut", tokenOut); if (paths.length === 0) { return defaultData; } // 2. sort and limit the count of paths, get paths to use const pathsToUse = await filterPathsToUseExactIn(paths, tokenAddressIn, tokenAmountIn, ts, stateStore().enableSplits); if (pathsToUse.length === 0) { return defaultData; } // 3. find the best amounts of each path and its steps for paths // this is the second step of processing route pools, after found all possible paths. const bestPathsAndAmounts = await findBestAmountsForPathsExactIn(pathsToUse, tokenAmountIn, ts, tokenOut); const route = { isETH: _isETH, isETHOut: _isETHOut, tokenIn: tokenAddressIn, tokenOut: tokenOut, found: bestPathsAndAmounts.found, amountIn: bestPathsAndAmounts.amountIn, amountOut: bestPathsAndAmounts.amountOut, quote: bestPathsAndAmounts.quote, paths: bestPathsAndAmounts.pathsWithAmounts, priceImpact: bestPathsAndAmounts.bestPriceImpact ?? ZERO, gas: bestPathsAndAmounts.gas, }; // cache swapRouteCache = { routePools: routePools, tokenIn: tokenAddressIn, amount: tokenAmountIn, cachedRoute: route, enableHops: stateStore().enableHops, enableSplits: stateStore().enableSplits, aquaPoolOnly: stateStore().aquaPoolOnly, }; return route; } async checkApproval(tokenAddress, amount, spender) { if (isETH(tokenAddress)) { return true; } // Always fetch fresh from blockchain const isV3 = stateStore().allowUniswapV3Pools; const routerAddress = spender || (isV3 ? ContractRegistry.getAddressByName("ROUTER_V3") : ContractRegistry.getAddressByName("ROUTER_V2")); const contract = await ContractLoader.loadContract(ContractTypes.TOKEN, tokenAddress); const allowance = await contract.allowance(stateStore().account, routerAddress); return amount.lte(allowance); } async approve(tokenAddress, amount, spender) { const isV3 = stateStore().allowUniswapV3Pools; const routerAddress = spender || (isV3 ? ContractRegistry.getAddressByName("ROUTER_V3") : ContractRegistry.getAddressByName("ROUTER_V2")); const contract = await ContractLoader.loadContract(ContractTypes.TOKEN, tokenAddress); const overrides = await createOverrides(ZERO, 2, true); return contract.populateTransaction.approve(routerAddress, amount, overrides); } // Get token permit data for EIP-2612 (with caching) async getTokenPermitData(tokenAddress) { // Check cache first if (this.tokenPermitCache.has(tokenAddress)) { return this.tokenPermitCache.get(tokenAddress) || null; } try { const multicall3 = getCachedMulticall3Contract(); const account = stateStore().account; const provider = stateStore().providerOrSigner; const chainId = BigNumber.from((await provider.getChainId()).toString()); // Prepare multicall3 calls for ERC20 permit functions const calls = [ { target: tokenAddress, allowFailure: true, callData: ERC20_PERMIT_FUNCTIONS.NONCES + defaultAbiCoder.encode(["address"], [account]).slice(2), // nonces(address) }, { target: tokenAddress, allowFailure: true, callData: ERC20_PERMIT_FUNCTIONS.NAME, // name() }, { target: tokenAddress, allowFailure: true, callData: ERC20_PERMIT_FUNCTIONS.VERSION, // version() }, ]; const results = await Promise.race([ multicall3.callStatic.aggregate3(calls), new Promise((resolve) => setTimeout(() => resolve(null), 3000)), ]); if (!results || results.length !== 3) { // Cache null result to avoid repeated failed attempts this.tokenPermitCache.set(tokenAddress, null); return null; } const [nonceResult, nameResult, versionResult] = results; // Check if all calls succeeded if (!nonceResult.success || !nameResult.success || !versionResult.success) { // Cache null result to avoid repeated failed attempts this.tokenPermitCache.set(tokenAddress, null); return null; } // Decode results const nonce = defaultAbiCoder.decode(["uint256"], nonceResult.returnData)[0]; const name = defaultAbiCoder.decode(["string"], nameResult.returnData)[0]; const version = defaultAbiCoder.decode(["string"], versionResult.returnData)[0]; const permitData = { nonce, name, version, chainId, }; // Cache the result this.tokenPermitCache.set(tokenAddress, permitData); return permitData; } catch (error) { console.error("Failed to fetch token permit data:", error); // Cache null result to avoid repeated failed attempts this.tokenPermitCache.set(tokenAddress, null); return null; } } // Sign permit for token approval async signPermit(tokenAddress, amount, spender, deadline) { try { const permitData = await this.getTokenPermitData(tokenAddress); if (!permitData) { console.warn("Token does not support permit"); return null; } const owner = stateStore().account; const amountToApprove = stateStore().enableLimitedUnlock ? amount : UINT256_MAX; const permitDeadline = deadline || getBlockTimestamp().add(BigNumber.from(3600)); // 1 hour if (!spender) { const isV3 = stateStore().allowUniswapV3Pools; spender = isV3 ? ContractRegistry.getAddressByName("ROUTER_V3") : ContractRegistry.getAddressByName("ROUTER_V2"); } const values = { owner: owner, spender: spender, value: amountToApprove, nonce: permitData.nonce, deadline: permitDeadline, }; // Use version from permit data (obtained from chain) const version = permitData.version; // Try different domain configurations const signer = stateStore().providerOrSigner; let signature = null; let usedDomain = null; // Use simple approach like requests.tsx console.log("signPermit permitData", permitData, permitData.chainId.toString(), "version", version, "spender", spender); const domain = { name: permitData.name, version: version, chainId: permitData.chainId, verifyingContract: tokenAddress, }; signature = await signer._signTypedData(domain, PERMIT_TYPES, values); usedDomain = domain; if (!signature) { throw new Error("Failed to generate permit signature"); } // Debug logging console.log("Permit signing successful:", { domain: usedDomain, values: { ...values, value: values.value.toString(), nonce: values.nonce.toString(), deadline: values.deadline.toString(), }, permitData: { ...permitData, nonce: permitData.nonce.toString(), chainId: permitData.chainId.toString(), }, }); return { signature: signature, data: { token: tokenAddress, owner: owner, spender: spender, amount: amountToApprove, nonce: permitData.nonce, deadline: permitDeadline, }, }; } catch (error) { console.error("Failed to sign permit:", error); return null; } } async swapExactInput(route, signature, to) { const networkName = stateStore().network; const swapCallback = ZERO_ADDRESS; let account = stateStore().account; const amountIn = route.amountIn; const amountOut = route.amountOut; const amountOutMin = getAmountMin(route.amountOut); const tokenIn = TokenRegistry.getToken(route.tokenIn); const tokenOut = TokenRegistry.getToken(route.tokenOut); const routerV2 = ContractRegistry.getContractByName("router"); const deadline = getBlockTimestamp().add(OVERRIDE_DEADLINE); // Handle ETH input. let amountETH = null; let [ethIn, ethOut] = [false, false]; if (tokenIn.address == Token.ETHER.address || route.isETH) { ethIn = true; amountETH = amountIn; } if (tokenOut.address == Token.ETHER.address || route.isETHOut) { ethOut = true; } //invariant(route, 'No route'); invariant(route && route.paths && route.paths.length !== 0, "Invalid route"); //console.log('swapExactInput paths', route.paths, 'ethIn', ethIn, 'ethOut', ethOut, 'amountETH', amountETH ? amountETH.toString() : 'null'); // // const swapCallback: string = getSwapCallbackAddress(); // if (swapCallback !== ZERO_ADDRESS) { // console.log('using swap callback', swapCallback); // } const swapCallbackData = "0x"; //let ethReceiver: string = ZERO_ADDRESS; const useV2RouterInterface = shouldUseV2RouterInterface(stateStore().network); const wETHAddress = getWETHAddress(); // handle ERC4626 wrappers const usingERC4626WrapperIn = !!tokenIn.wrapper && tokenIn.wrapper !== ZERO_ADDRESS; const usingERC4626WrapperOut = !!tokenOut.wrapper && tokenOut.wrapper !== ZERO_ADDRESS; const hasERC4626Wrapper = usingERC4626WrapperIn || usingERC4626WrapperOut; console.log("[debug] hasERC4626Wrapper", hasERC4626Wrapper, "usingERC4626WrapperIn", usingERC4626WrapperIn, "usingERC4626WrapperOut", usingERC4626WrapperOut); /* // update minimum output amount for ERC4626 if (usingERC4626WrapperOut && tokenOut.wrapper) { const rateShareToAssets = getERC4626Rate(tokenOut.wrapper) ?? ETHER; // share to assets amountOutMin = amountOutMin.mul(ETHER).div(rateShareToAssets); // convert asset -> share } */ if (stateStore().allowUniswapV3Pools) { let routerV3 = null; // find router contract to use { if (hasERC4626Wrapper) { routerV3 = ContractRegistry.getContractByName("wrapper_router_v3"); } if (!routerV3) { routerV3 = ContractRegistry.getContractByName("router_v3"); } } console.log("[debug] using routerV3", routerV3?.address); if (routerV3 && (networkName === ZKSYNC_MAINNET || networkName === SOPHON_MAINNET || networkName === LINEA_MAINNET || networkName === SCROLL_MAINNET)) { const routerV3Address = routerV3.address; // const ethUnwrapRecipient = ethOut ? account : ZERO_ADDRESS; let extraRecipientForV3Router = usingERC4626WrapperOut ? account : ZERO_ADDRESS; const allPathsData = []; /* const steps: any[] = []; const pathV3 = { steps: steps, tokenIn: firstStep.tokenIn, amountIn: firstStep.amountIn, }; pathsArrayV3.push(pathV3); */ for (const path of route.paths) { // all steps of current path const stepsData = []; // check step invariant(path.stepsWithAmount.length !== 0, "Route path has no steps"); // It's important to cast input token of the path to native ETH (zero address), // if the input token is native ETH. // Otherwise ETH is sent to the router, and the router will trying to transfer wETH. const pathTokenInRoute = tokenIn.address === Token.ETHER.address ? ZERO_ADDRESS : tokenIn.address; //path.stepsWithAmount[0].tokenIn; const pathTokenIn = ethIn ? wETHAddress : pathTokenInRoute; //if (ethIn) { // invariant(pathTokenInRoute === wETHAddress, "Swap input is ETH but token is not wETH"); //} //if (usingERC4626WrapperIn && tokenIn.wrapper) { // path.stepsWithAmount[0].tokenIn = tokenIn.wrapper; //} //console.log('wETHAddress', wETHAddress, 'ethIn', ethIn); path.stepsWithAmount.forEach((stepCurrent, index) => { // get the next step, or null if last const nextStep = index < path.stepsWithAmount.length ? path.stepsWithAmount[index + 1] : null; // check for vaults const nextStepUseVault = nextStep && !nextStep.pool.isUniswap && !nextStep.pool.isV2 && !nextStep.pool.isV3; const currentStepUseVault = stepCurrent && !stepCurrent.pool.isUniswap && !stepCurrent.pool.isV2 && !stepCurrent.pool.isV3; //console.log('nextStep', nextStep, 'index', index, 'length', path.stepsWithAmount.length, 'nextStepUseVault', nextStepUseVault, 'currentStepUseVault', currentStepUseVault); //const stepRecipient = stepNext ? stepNext.pool : account; let stepRecipient; // Determine withdraw mode, to withdraw native ETH or wETH on last step. // 0 - vault internal transfer // 1 - withdraw and unwrap to naitve ETH // 2 - withdraw and wrap to wETH //const withdrawMode: number = (isLastStep ? (ethOut ? 1 : 2) : (isNextV2 ? 2 : 0)); let withdrawMode = null; if (currentStepUseVault) { // current step using vault if (nextStep) { // has next step if (nextStepUseVault) { // both use vault stepRecipient = nextStep.pool.pool; // send to the next pool via vault internal transfer withdrawMode = 0; // vault internal transfer } else { // next step NOT using vault if (nextStep.pool.isUniswap) { // withdraw to router so UniswapV3 callback can transfer tokens to pool stepRecipient = routerV3Address; withdrawMode = 2; // get WETH } else { // withdraw to the next pool stepRecipient = nextStep.pool.pool; withdrawMode = 2; // get WETH } } } else { // no next step, withdraw to user wallet stepRecipient = usingERC4626WrapperOut ? routerV3Address : account; // need router to unwrap ERC4626 if (usingERC4626WrapperOut) { extraRecipientForV3Router = account; // unwrap and send to user wallet eventually } withdrawMode = ethOut ? 1 : 2; } } else { // current step NOT using vault if (nextStep) { // has next step if (nextStepUseVault) { // next step use vault // send to router and the router will deposit to vault if (stepCurrent.pool.isUniswap) { stepRecipient = routerV3Address; withdrawMode = null; // default - get WETH } else { stepRecipient = routerV3Address; withdrawMode = 2; // get WETH } } else { // next step NOT using vault if (nextStep.pool.isUniswap) { // send to router so UniswapV3 callback can transfer tokens to pool if (stepCurrent.pool.isUniswap) { stepRecipient = routerV3Address; withdrawMode = null; // default - get WETH } else { stepRecipient = routerV3Address; withdrawMode = 2; // get WETH } } else { // send to the next pool if (stepCurrent.pool.isUniswap) { stepRecipient = nextStep.pool.pool; withdrawMode = null; // default - get WETH } else { stepRecipient = nextStep.pool.pool; withdrawMode = 2; // get WETH } } } } else { // no next step, send to user wallet if (stepCurrent.pool.isUniswap) { stepRecipient = ethOut || usingERC4626WrapperOut ? routerV3Address : account; // need router to unwrap ETH or ERC4626 withdrawMode = null; // not supported, use `ethUnwrapRecipient` extraRecipientForV3Router = account; // unwrap and send to user wallet eventually } else { stepRecipient = usingERC4626WrapperOut ? routerV3Address : account; // need router to unwrapERC4626 if (usingERC4626WrapperOut) { extraRecipientForV3Router = account; // unwrap and send to user wallet eventually } withdrawMode = ethOut ? 1 : 2; } } } /* if (!currentStepUseVault && nextStepUseVault) { // send to router if current step is NOT using vault but next step IS using vault // need router to deposit tokens to vault stepRecipient = routerV3.address; } */ // this is identifier for range pool as well const recipientV3 = stepCurrent.pool.isUniswap ? stepRecipient : ZERO_ADDRESS; //console.log('stepRecipient', stepRecipient, 'stepCurrent.pool.isUniswap', stepCurrent.pool.isUniswap, 'stepCurrent', stepCurrent); const swapData = withdrawMode !== null ? defaultAbiCoder.encode(["address", "address", "uint8"], [stepCurrent.tokenIn, stepRecipient, withdrawMode] // tokenIn, to, withdraw mode ) : "0x"; const stepData = { pool: stepCurrent.pool.pool, data: swapData, callback: swapCallback, callbackData: swapCallbackData, useVault: currentStepUseVault, tokenOut: stepCurrent.tokenOut, // TODO add this zeroForOne: stepCurrent.zeroForOne, // TODO add this recipient: recipientV3, sqrtPriceLimitX96: ZERO, }; //console.log('stepData', index, stepData, 'withdrawMode', withdrawMode, 'stepRecipient', stepRecipient, 'swapData', swapData, 'token0', stepCurrent.tokenIn < stepCurrent.tokenOut ? stepCurrent.tokenIn : stepCurrent.tokenOut, 'ethIn', ethIn, 'amountETH', amountETH?.toString(), 'stepCurrent.pool.isUniswap', stepCurrent.pool.isUniswap); stepsData.push(stepData); }); //console.log('current path steps', path.stepsWithAmount, 'stepsData', stepsData, 'pathTokenIn', pathTokenIn, 'path.amountIn', path.amountIn); // current path const pathData = { steps: stepsData, tokenIn: pathTokenIn, amountIn: path.amountIn, //path.amountIn, }; allPathsData.push(pathData); } //console.log('extraRecipientForV3Router', extraRecipientForV3Router, 'allPathsData', allPathsData, 'amountIn', amountIn); let permitParams = null; const hasSignature = signature !== null && signature !== undefined; if (hasSignature) { permitParams = generateSplitPermitParams([signature])[0]; } const usePermit = hasSignature && permitParams !== null; const overrides = await createOverrides(ZERO, 2, true, ethIn ? { value: amountETH } : undefined, { swapAmount: amountIn, swapToken: tokenIn.address, }); //console.log('overrides', overrides); let rawTransaction; if (usePermit) { if (hasERC4626Wrapper) { // Permit & Wrapper rawTransaction = await routerV3.populateTransaction.swapWithPermitAndWrapper(allPathsData, amountOutMin, deadline, permitParams, // add permit params extraRecipientForV3Router, tokenIn.wrapper ?? ZERO_ADDRESS, tokenOut.wrapper ?? ZERO_ADDRESS, overrides); } else { // Permit rawTransaction = await routerV3.populateTransaction.swapWithPermit(allPathsData, amountOutMin, deadline, permitParams, // add permit params extraRecipientForV3Router, overrides); } } else { if (hasERC4626Wrapper) { // Wrapper console.log("[debug] swapWithWrapper tokenIn.wrapper", tokenIn.wrapper, "tokenOut.wrapper", tokenOut.wrapper, "extraRecipientForV3Router", extraRecipientForV3Router, "amountOutMin", amountOutMin.toString()); rawTransaction = await routerV3.populateTransaction.swapWithWrapper(allPathsData, amountOutMin, deadline, extraRecipientForV3Router, tokenIn.wrapper ?? ZERO_ADDRESS, tokenOut.wrapper ?? ZERO_ADDRESS, overrides); } else { // Swap Only //console.log('[debug] v3 swap allPathsData', allPathsData, 'amountOutMin', amountOutMin.toString(), 'deadline', deadline.toString(), 'extraRecipientForV3Router', extraRecipientForV3Router, 'overrides', overrides); rawTransaction = await routerV3.populateTransaction.swap(allPathsData, amountOutMin, deadline, extraRecipientForV3Router, overrides); if (!rawTransaction.chainId) { try { rawTransaction.chainId = Number(await stateStore().providerOrSigner.getChainId()); } catch { rawTransaction.chainId = undefined; } } } } return rawTransaction; } else { console.error("Specified V3 router not exists", hasERC4626Wrapper); } } //////////////////////////////////////////////////////////////////////////////////////////////////// //// V2 Router //////////////////////////////////////////////////////////////////////////////////////////////////// let hasAquaPoolInPath = false; const pathsData = route.paths.map((path) => { invariant(path.stepsWithAmount.length !== 0, "Route path has no steps"); // It's important to cast input token of the path to native ETH (zero address), // if the input token is native ETH. // Otherwise ETH is sent to the router, and the router will trying to transfer wETH. const pathTokenInRoute = path.stepsWithAmount[0].tokenIn; const pathTokenIn = ethIn ? ZERO_ADDRESS : pathTokenInRoute; if (ethIn) { invariant(pathTokenInRoute === wETHAddress, "Swap input is ETH but token is not wETH"); } // Path data const pathData = { steps: path.stepsWithAmount.map((step, i) => { const isLastStep = i == path.stepsWithAmount.length - 1; const nextStep = isLastStep ? undefined : path.stepsWithAmount[i + 1]; //const isV2 = step.pool.isV2; const isNextV2 = useV2RouterInterface && nextStep && nextStep.pool.isV2; if (step.pool.poolType === PoolTypes.AQUA) { hasAquaPoolInPath = true; } const stepTo = isLastStep ? to : path.stepsWithAmount[i + 1].pool.pool; // Determine withdraw mode, to withdraw native ETH or wETH on last step. // 0 - vault internal transfer // 1 - withdraw and unwrap to naitve ETH // 2 - withdraw and wrap to wETH const withdrawMode = isLastStep ? ethOut ? 1 : 2 : isNextV2 ? 2 : 0; const data = defaultAbiCoder.encode(["address", "address", "uint8"], [step.tokenIn, stepTo, withdrawMode] // tokenIn, to, withdraw mode ); // Step data let stepData; if (useV2RouterInterface) { stepData = { pool: step.pool.pool, data: data, callback: swapCallback, callbackData: swapCallbackData, useVault: !step.pool.isV2, }; } else { stepData = { pool: step.pool.pool, data: data, callback: swapCallback, callbackData: swapCallbackData, }; } return stepData; }), tokenIn: pathTokenIn, amountIn: path.amountIn, }; return pathData; }); //console.log('pathsData', pathsData, 'route', route); // permit let permitParams = null; const hasSignature = signature !== null; if (hasSignature) { permitParams = generateSplitPermitParams([signature])[0]; } const usePermit = hasSignature && permitParams !== null; // console.log('usePermit', usePermit) // const paymasterParams = Requests.getPayMasterParams() const estimateGas = ZERO; try { // swap() params: // paths: SwapPath[] // amountOutMin: uint // deadline: uint const args = [pathsData, amountOutMin, deadline]; const doNotSetFrom = false; if (usePermit) { args.push(permitParams); // add permit params if (ethIn) { if (doNotSetFrom) { args.push({ value: amountETH, }); } else { args.push({ value: amountETH, from: account, }); } } else { if (!doNotSetFrom) { args.push({ from: account, }); } } // estimateGas = await router.estimateGas.swapWithPermit(...args); } else { if (ethIn) { if (doNotSetFrom) { args.push({ value: amountETH, }); } else { args.push({ value: amountETH, from: account, }); } } else { if (!doNotSetFrom) { args.push({ from: account, }); } } } } catch (error) { console.warn("swapExactInput: error on estimate gas", error, "pathsData", pathsData, "amountIn", amountIn.toString(), "ethIn", ethIn, "ethOut", ethOut, "tokenIn", tokenIn, "tokenOut", tokenOut); // Note: now handled by error window. // disable signature in case permit of the token is broken. //if (usePermit) { // setDisableSplitSignature(true); //} } console.log("estimateGas", estimateGas.toString()); try { const isExpensive = pathsData.length > 1; const overrides = await createOverrides(ZERO, isExpensive ? 3 : hasAquaPoolInPath ? 2 : 1, true, ethIn ? { value: amountETH } : undefined, { swapAmount: amountIn, swapToken: tokenIn.address, }); // legacy transaction may failure and Scroll network not support EIP1559,this behavior will reduce swap failure // if (networkName === SCROLL_MAINNET && !usePermit && stateStore().route?.gasEstimate.gt(ZERO)) { // overrides.gasLimit = stateStore().route?.gasEstimate.mul(105).div(100) // console.warn(`override gasLimit ${stateStore().route?.gasEstimate.toNumber()} to ${overrides.gasLimit?.toNumber()}`) // } let rawTransaction; if (usePermit) { rawTransaction = await routerV2.populateTransaction.swapWithPermit(pathsData, amountOutMin, deadline, permitParams, // add permit params //ethReceiver, overrides); } else { rawTransaction = await routerV2.populateTransaction.swap(pathsData, amountOutMin, deadline, //ethReceiver, overrides); } return rawTransaction; } catch (error) { console.warn("SwapExactInput error", error); throw error; } } /** * Get all registered tokens * @returns Array containing all registered tokens */ getAllTokens() { return TokenRegistry.allTokens.slice(); // Return copy to avoid external modification } /** * Get all verified tokens (including tokens from default token list) * @returns Array containing all verified tokens, including ETH */ getVerifiedTokens() { return TokenRegistry.getVerifiedTokens(); } /** * Get all indexed tokens (tokens displayed in UI) * @returns Array containing all indexed tokens, including ETH */ getIndexedTokens() { return TokenRegistry.getIndexedTokens(); } /** * Get hidden listed tokens * @returns Array containing hidden listed tokens */ getHiddenListedTokens() { return TokenRegistry.getHiddenListedTokens(); } /** * Get token by address * @param address Token address * @returns Token object, or null if not found */ getTokenByAddress(address) { return TokenRegistry.getTokenByAddress(address); } /** * Get token by symbol * @param symbol Token symbol * @returns Token object, or null if not found */ getTokenBySymbol(symbol) { return TokenRegistry.getTokenBySymbol(symbol); } /** * Check if token is verified * @param address Token address * @returns True if token is verified */ isTokenVerified(address) { return TokenRegistry.isTokenVerified(address); } /** * Check if token is indexed (displayed in UI) * @param address Token address * @returns True if token is indexed */ isTokenIndexed(address) { return TokenRegistry.isTokenIndexed(address); } /** * Lookup and register token from blockchain * @param address Token address * @param shouldRegister Whether to register in registry * @param isIndexed Whether to mark as indexed * @returns [Token object or null, whether it's newly fetched] */ async lookupTokenByAddress(address, shouldRegister = true, isIndexed = false) { const provider = stateStore().providerOrSigner; return TokenRegistry.lookupTokenByAddress(provider, address, shouldRegister, isIndexed); } /** * Get route tokens list (base tokens used for routing calculation) * @returns Array containing route token addresses */ getRouteTokens() { return getRouteTokens(this.config.network); } /** * Get wETH address for current network * @returns wETH token address */ getWETHAddress() { return getWETHAddress(this.config.network); } } //# sourceMappingURL=index.js.map