@syncswap/sdk
Version:
SyncSwap TypeScript SDK for building DeFi applications
958 lines • 45.6 kB
JavaScript
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